Every time I wanted to tweak a margin or color during development, I'd do the same thing:
open DevTools → find the element → adjust the value → like it → copy it → switch to my editor → find the right file → paste it.
That's 7 steps for a one-line change. I built LiveStyleSync to make it one.
What it does
LiveStyleSync adds a small panel to your Vite dev environment. Click any element on the page, edit its CSS properties in the panel, and the change writes directly to your source file. Vite HMR picks it up instantly.
No copy-pasting. No switching tabs.
Click element → edit value → Vite HMR updates browser → source file updated
Quick start
npm install livestylesync-overlay livestylesync-vite-plugin
// vite.config.ts
import { liveStyleSync } from "livestylesync-vite-plugin";
export default defineConfig({
plugins: [liveStyleSync()],
});
// main.ts
import { mount } from "livestylesync-overlay";
if (import.meta.env.DEV) {
mount();
}
That's it. A panel appears in the corner of your app.
How it works under the hood
This was the interesting part to build.
Reading styles: The overlay traverses document.styleSheets (the CSSOM) to find every CSS rule that matches the clicked element — including rules inside @media, @container, and pseudo-state rules like :hover. It resolves which source file owns each rule via Vite's data-vite-dev-id attribute.
Writing back: When you apply a change, it goes over WebSocket to the Vite plugin. The plugin uses PostCSS to parse the source file as an AST, find the exact rule and declaration, patch the value, and write the file back. PostCSS handles all the edge cases — complex selectors, nested rules, SCSS nesting syntax.
Edge cases I had to handle:
- Universal selectors (
*,::before) matching every element — had to skip those -
CSSContainerRulenot being in TypeScript's lib — duck-typed viaconditionTextproperty - Inline styles overriding class styles after restore — had to explicitly clear them
- HMR timing after file write — replaced hardcoded
setTimeout(400)with a server-sentpatchedconfirmation
Features
- @media and @container tabs — separate tab per breakpoint/container
-
Pseudo-state editing —
:hover,:focus,:active(uses CSS class injection trick) -
CSS custom properties — browse and edit
:rootvariables live -
SCSS $variables — server scans all
.scssfiles, lets you edit$primaryetc. - Session history — git-style diff of all changes, undo by batch
- Create new rules — add CSS to elements that have no source rule yet
- Tailwind detection — warns instead of trying to patch generated utilities
CSS format support
| Format | Supported |
|---|---|
Plain .css
|
✅ |
.scss |
✅ |
| CSS Modules | ✅ |
Vue <style scoped>
|
✅ |
| Tailwind utilities | ⚠️ detected, warns |
Works with any framework on Vite
React, Vue, Nuxt, SvelteKit, Astro, Solid — anything that uses Vite as the dev server. The overlay has no React peer dependency (Preact is bundled and isolated).
Try it
GitHub: https://github.com/Artyx71/livestylesync
npm:
npm install livestylesync-overlay livestylesync-vite-plugin
I'd love feedback — especially if you try it on a non-React setup or hit an edge case with your CSS structure. Open an issue or drop a comment here.
Top comments (0)