<?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: Rudra Patel</title>
    <description>The latest articles on DEV Community by Rudra Patel (@vyeos).</description>
    <link>https://dev.to/vyeos</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%2F3773565%2Fffafac4d-ef1e-476d-90b6-4ba98300b278.png</url>
      <title>DEV Community: Rudra Patel</title>
      <link>https://dev.to/vyeos</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vyeos"/>
    <language>en</language>
    <item>
      <title>I Built an AST-Based Tool to Migrate CSS to Tailwind for React (like frameworks)</title>
      <dc:creator>Rudra Patel</dc:creator>
      <pubDate>Sun, 15 Feb 2026 09:59:05 +0000</pubDate>
      <link>https://dev.to/vyeos/i-built-an-ast-based-tool-to-migrate-css-to-tailwind-for-react-like-frameworks-he2</link>
      <guid>https://dev.to/vyeos/i-built-an-ast-based-tool-to-migrate-css-to-tailwind-for-react-like-frameworks-he2</guid>
      <description>&lt;h2&gt;
  
  
  Making the Website
&lt;/h2&gt;

&lt;p&gt;A few days ago, I rebuilt my portfolio using Astro (as I wanted to try it). But as a beginner mistake, I used AI from start to finish so it used vanilla CSS. I didn't consider it a problem then because I didn't had to change it now or later. &lt;/p&gt;

&lt;p&gt;The problem came when I decided to add blogs to my website. Blogs are dynamic, they have to be fetched from a cms and have to be displayed. Astro is not very good at dynamic content. That forced me to move everything to NextJS. I did most of the migrations myself as it was just to add a&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function page(props...){
...
return(...)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that I shifted to NextJS, I wanted to use tailwind instead of vanilla CSS (devs having random thoughts). I didn't want to manually rewrite all my CSS into Tailwind classes. So instead of rewriting styles - &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I built a migration tool.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;The issue I had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;External CSS files&lt;/li&gt;
&lt;li&gt;Class-based styles&lt;/li&gt;
&lt;li&gt;Hundreds of simple declarations like:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;display: flex;
justify-content: center;
font-weight: bold;
margin: 16px;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rewriting all of that into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;className="flex justify-center font-bold m-4"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;was not happening manually. This gave me an idea of creating a vanilla to TW migration tool. Starting my chat with AI, the first thing it said was to not use regex and use AST instead.&lt;/p&gt;

&lt;p&gt;Using regex, I just search and replace CSS patterns. But JSX is not plain HTML. It breaks instantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div style={{ display: "flex" }} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you regex that, you’ll:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Break syntax&lt;/li&gt;
&lt;li&gt;Miss nested cases&lt;/li&gt;
&lt;li&gt;Destroy formatting&lt;/li&gt;
&lt;li&gt;Fail on conditional classNames&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So it showed me the correct route.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: AST-Based Transformation
&lt;/h2&gt;

&lt;p&gt;Instead of treating code as text, AST treats it as structured data. I used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/babel"&gt;@babel&lt;/a&gt;/parser&lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/babel"&gt;@babel&lt;/a&gt;/traverse&lt;/li&gt;
&lt;li&gt;postcss&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allowed me to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse React files into AST&lt;/li&gt;
&lt;li&gt;Detect style={{}} objects&lt;/li&gt;
&lt;li&gt;Extract properties safely&lt;/li&gt;
&lt;li&gt;Convert them to Tailwind utilities&lt;/li&gt;
&lt;li&gt;Remove the style attribute&lt;/li&gt;
&lt;li&gt;Merge with existing className&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All without breaking JSX.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;Example&lt;/p&gt;

&lt;p&gt;Before&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div style={{ display: "flex", justifyContent: "center" }} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div className="flex justify-center" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic external CSS&lt;/li&gt;
&lt;li&gt;Simple class selectors&lt;/li&gt;
&lt;li&gt;className merging&lt;/li&gt;
&lt;li&gt;Dry-run mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tool
&lt;/h2&gt;

&lt;p&gt;I packaged it as: &lt;strong&gt;css-to-tailwind-react&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install css-to-tailwind-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pnpm add css-to-tailwind-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bun add css-to-tailwind-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx css-to-tailwind-react ./src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pnpm dlx css-to-tailwind-react ./src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bunx css-to-tailwind-react ./src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Current Limitations (in Beta)
&lt;/h2&gt;

&lt;p&gt;It currently skips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Media queries&lt;/li&gt;
&lt;li&gt;Pseudo selectors (:hover, :focus)&lt;/li&gt;
&lt;li&gt;Nested selectors&lt;/li&gt;
&lt;li&gt;SCSS&lt;/li&gt;
&lt;li&gt;Animations&lt;/li&gt;
&lt;li&gt;CSS variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Right now it handles simple and common CSS rules reliably.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reason
&lt;/h2&gt;

&lt;p&gt;This wasn’t just about avoiding typing. This was about solving a problem that I faced. Many devs are confused about what should their next project be. Instead of searching the web and asking advice to others they can just create a solution to a problem they face.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Source
&lt;/h2&gt;

&lt;p&gt;It’s open source and available here:&lt;/p&gt;

&lt;p&gt;GitHub:&lt;br&gt;
&lt;a href="http://github.com/vyeos/css-to-tailwind-react" rel="noopener noreferrer"&gt;http://github.com/vyeos/css-to-tailwind-react&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s also published on npm.&lt;/p&gt;

&lt;p&gt;If you try it on a real project, I’d love feedback.&lt;/p&gt;

&lt;p&gt;Especially:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Edge cases it breaks&lt;/li&gt;
&lt;li&gt;CSS patterns it misses&lt;/li&gt;
&lt;li&gt;Tailwind mappings that feel wrong&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;Planned improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Media query support&lt;/li&gt;
&lt;li&gt;Pseudo selector conversion&lt;/li&gt;
&lt;li&gt;CSS Modules support&lt;/li&gt;
&lt;li&gt;Smarter Tailwind scale matching&lt;/li&gt;
&lt;li&gt;Interactive CLI mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is still beta.&lt;/p&gt;

&lt;p&gt;But it works surprisingly well for simple migrations.&lt;/p&gt;

&lt;p&gt;If you test it, let me know what breaks 🙂&lt;/p&gt;

</description>
      <category>css</category>
      <category>nextjs</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
