<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrew Gabaraev</title>
    <description>The latest articles on DEV Community by Andrew Gabaraev (@artyx71).</description>
    <link>https://dev.to/artyx71</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3942664%2F22efa080-0cc2-4291-acc0-c5d0b5cca970.png</url>
      <title>DEV Community: Andrew Gabaraev</title>
      <link>https://dev.to/artyx71</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/artyx71"/>
    <language>en</language>
    <item>
      <title>I built a Vite plugin that writes CSS changes back to your source files</title>
      <dc:creator>Andrew Gabaraev</dc:creator>
      <pubDate>Wed, 20 May 2026 16:06:18 +0000</pubDate>
      <link>https://dev.to/artyx71/i-built-a-vite-plugin-that-writes-css-changes-back-to-your-source-files-4omp</link>
      <guid>https://dev.to/artyx71/i-built-a-vite-plugin-that-writes-css-changes-back-to-your-source-files-4omp</guid>
      <description>&lt;p&gt;Every time I wanted to tweak a margin or color during development, I'd do the same thing:&lt;br&gt;
open DevTools → find the element → adjust the value → like it → copy it → switch to my editor → find the right file → paste it.&lt;/p&gt;

&lt;p&gt;That's 7 steps for a one-line change. I built LiveStyleSync to make it one.&lt;/p&gt;


&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;No copy-pasting. No switching tabs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Click element → edit value → Vite HMR updates browser → source file updated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;livestylesync-overlay livestylesync-vite-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vite.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;liveStyleSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;livestylesync-vite-plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;liveStyleSync&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;livestylesync-overlay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEV&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. A panel appears in the corner of your app.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works under the hood
&lt;/h2&gt;

&lt;p&gt;This was the interesting part to build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading styles:&lt;/strong&gt; The overlay traverses &lt;code&gt;document.styleSheets&lt;/code&gt; (the CSSOM) to find every CSS rule that matches the clicked element — including rules inside &lt;code&gt;@media&lt;/code&gt;, &lt;code&gt;@container&lt;/code&gt;, and pseudo-state rules like &lt;code&gt;:hover&lt;/code&gt;. It resolves which source file owns each rule via Vite's &lt;code&gt;data-vite-dev-id&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing back:&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edge cases I had to handle:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Universal selectors (&lt;code&gt;*&lt;/code&gt;, &lt;code&gt;::before&lt;/code&gt;) matching every element — had to skip those&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CSSContainerRule&lt;/code&gt; not being in TypeScript's lib — duck-typed via &lt;code&gt;conditionText&lt;/code&gt; property&lt;/li&gt;
&lt;li&gt;Inline styles overriding class styles after restore — had to explicitly clear them&lt;/li&gt;
&lt;li&gt;HMR timing after file write — replaced hardcoded &lt;code&gt;setTimeout(400)&lt;/code&gt; with a server-sent &lt;code&gt;patched&lt;/code&gt; confirmation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/media"&gt;@media&lt;/a&gt; and @container tabs&lt;/strong&gt; — separate tab per breakpoint/container&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pseudo-state editing&lt;/strong&gt; — &lt;code&gt;:hover&lt;/code&gt;, &lt;code&gt;:focus&lt;/code&gt;, &lt;code&gt;:active&lt;/code&gt; (uses CSS class injection trick)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSS custom properties&lt;/strong&gt; — browse and edit &lt;code&gt;:root&lt;/code&gt; variables live&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SCSS $variables&lt;/strong&gt; — server scans all &lt;code&gt;.scss&lt;/code&gt; files, lets you edit &lt;code&gt;$primary&lt;/code&gt; etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session history&lt;/strong&gt; — git-style diff of all changes, undo by batch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create new rules&lt;/strong&gt; — add CSS to elements that have no source rule yet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind detection&lt;/strong&gt; — warns instead of trying to patch generated utilities&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  CSS format support
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Supported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Plain &lt;code&gt;.css&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.scss&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSS Modules&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vue &lt;code&gt;&amp;lt;style scoped&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tailwind utilities&lt;/td&gt;
&lt;td&gt;⚠️ detected, warns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Works with any framework on Vite
&lt;/h2&gt;

&lt;p&gt;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).&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Artyx71/livestylesync" rel="noopener noreferrer"&gt;https://github.com/Artyx71/livestylesync&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;npm:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;livestylesync-overlay livestylesync-vite-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;




</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
