</>
DevToolHub
Close-up of code on a dark monitor screen with colorful syntax

How to Migrate CSS to Tailwind (Step-by-Step, No Rewrites)

·9 min read
Quick answer: Don't rewrite everything at once. Install Tailwind alongside your existing CSS, convert new components in Tailwind, and migrate old components one at a time starting with layout utilities (flex, grid, spacing). Use @apply as a bridge for complex selectors you aren't ready to inline. A typical 50-page site takes 2-4 weeks of incremental migration without breaking anything.

I migrated a 14,000-line CSS codebase to Tailwind over three weeks. The first mistake I made was trying to convert everything in one weekend. By Sunday night I had broken the navigation, the footer was missing its background, and three form components had lost their hover states. I reverted the whole thing Monday morning.

The second attempt worked. I installed Tailwind alongside the existing CSS, converted one component per day, and deleted old CSS rules only after verifying each replacement. Zero downtime, zero regressions. Here's the exact process.

Before You Start: The Pre-Migration Audit

Don't touch any code until you know what you're working with. Run a quick audit of your current CSS:

1. Count your CSS. Check total lines across all stylesheets. Under 2,000 lines? You can probably migrate in a week. Over 10,000? Plan for 3-4 weeks.

2. Identify dead CSS. Tools like PurgeCSS (standalone) or Chrome DevTools' Coverage tab will show you how much CSS is actually used. On the site I migrated, 38% of the CSS was dead --- rules from deleted pages, old experiments, vendor prefixes for browsers we dropped years ago. Delete dead CSS before you start. No point in migrating rules nobody uses.

3. List your custom values. Go through your stylesheets and note any recurring custom values: specific hex colors, font sizes, spacing values, breakpoints. These become your tailwind.config.js customizations. If your codebase uses #2563EB everywhere, map it to a semantic name like primary in Tailwind's config.

4. Check for CSS-in-JS or preprocessors. If you're using Sass, Less, or styled-components, the migration path is slightly different. Sass nesting and variables map well to Tailwind. CSS-in-JS libraries require converting from JavaScript objects back to class strings, which is more work.

Step 1: Install Tailwind Alongside Existing CSS

This is the critical part --- Tailwind runs in parallel with your old CSS, not instead of it.

npm install -D tailwindcss @tailwindcss/postcss postcss
npx tailwindcss init

Create your Tailwind entry point (tailwind.css or add to your existing entry):

@import "tailwindcss";

Configure tailwind.config.js to scan your template files:

module.exports = {
  content: [
    './src/**/*.{html,js,jsx,ts,tsx}',
    './pages/**/*.{html,js,jsx,ts,tsx}',
  ],
  theme: {
    extend: {},
  },
}

Import your existing CSS after the Tailwind import so your old styles still take precedence where needed. Both systems coexist. Tailwind utilities and your old classes work side by side on the same elements.

At this point, nothing changes visually. Your site looks identical. That's the goal.

Step 2: Map Your CSS Properties to Tailwind

Here's the reference table I kept open during my entire migration. These are the most common CSS properties and their Tailwind equivalents:

CSS PropertyExample ValueTailwind ClassNotes
display: flex---flexSame for grid, block, inline-flex, etc.
justify-content: center---justify-centerjustify-between, justify-around, justify-end
align-items: center---items-centeritems-start, items-end, items-stretch
gap: 1rem16pxgap-4Tailwind uses 4 = 1rem (16px). gap-2 = 0.5rem
padding: 1rem16pxp-4px-4 (horizontal), py-4 (vertical), pt-4 (top)
margin: 0 auto---mx-autom-4 (all), mt-4 (top), mb-0 (bottom zero)
font-size: 1.25rem20pxtext-xltext-sm (14px), text-base (16px), text-lg (18px)
font-weight: 700boldfont-boldfont-normal (400), font-semibold (600)
color: #374151gray-700text-gray-700Uses Tailwind's color palette
background-color: #fffwhitebg-whitebg-gray-100, bg-blue-500, etc.
border-radius: 0.5rem8pxrounded-lgrounded (4px), rounded-xl (12px), rounded-full
width: 100%---w-fullw-1/2 (50%), w-screen, w-auto
max-width: 1280px---max-w-7xlmax-w-sm (384px), max-w-lg (512px)
position: relative---relativeabsolute, fixed, sticky
box-shadowsmallshadow-mdshadow-sm, shadow-lg, shadow-xl, shadow-none
Bookmark this or print it. You'll reference it hundreds of times during migration. For anything not in this table, the CSS to Tailwind converter handles the translation automatically --- paste a CSS rule, get the Tailwind classes.

Step 3: Migrate Layout First

Start with layout utilities because they're the easiest to convert and give you the most visible progress.

Flexbox containers. Find every display: flex in your CSS. Each one becomes a flex class plus whatever justify/align/gap utilities you need. A rule like this:

.header-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 1.5rem;
}

Becomes:

<nav class="flex items-center justify-between px-6">

That's 4 CSS properties replaced with 4 Tailwind classes. Delete the .header-nav rule from your stylesheet.

Grid layouts. Same approach. display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem; becomes grid grid-cols-3 gap-8.

Spacing. This is the biggest category by line count. Every margin and padding rule in your CSS converts to m- and p- utilities. I migrated a 14,000-line codebase and roughly 40% of the lines were some form of spacing declaration. Converting spacing alone eliminated 5,600 lines of CSS.

After layout, your CSS file should be noticeably shorter and your HTML will have Tailwind classes on most structural elements.

Step 4: Migrate Typography and Colors

Font sizes and weights. Map your existing font sizes to Tailwind's scale. If your codebase uses arbitrary pixel values (13px, 15px, 17px), round to the nearest Tailwind size or extend the config:

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      fontSize: {
        '13': '0.8125rem',
        '15': '0.9375rem',
        '17': '1.0625rem',
      }
    }
  }
}

Colors. This is where tailwind.config.js customization matters most. If your brand uses #2563EB for primary actions, don't use Tailwind's blue-600 (which is #2563EB by coincidence, but might not match your exact shade). Add your colors to the config:

theme: {
  extend: {
    colors: {
      primary: '#2563EB',
      secondary: '#7C3AED',
      surface: '#F9FAFB',
    }
  }
}

Now bg-primary, text-secondary, and bg-surface work everywhere and match your design system exactly.

Step 5: Handle Responsive Breakpoints

Vanilla CSS media queries migrate to Tailwind's responsive prefixes. Here's the mapping:

/* Old CSS */
.container {
  padding: 1rem;
}
@media (min-width: 768px) {
  .container {
    padding: 2rem;
  }
}
@media (min-width: 1024px) {
  .container {
    padding: 3rem;
  }
}
<!-- Tailwind -->
<div class="p-4 md:p-8 lg:p-12">

Three media query blocks collapse into three classes. This is where Tailwind's density wins --- responsive CSS in vanilla files is verbose because every breakpoint is a separate block with a selector.

Tailwind's default breakpoints are sm (640px), md (768px), lg (1024px), xl (1280px), and 2xl (1536px). If your old CSS uses different breakpoints (say 720px and 960px), either adjust to the nearest Tailwind breakpoint or customize the config.

Step 6: Use @apply for Complex Selectors

Some CSS patterns don't convert cleanly to inline utilities. Pseudo-elements (::before, ::after with complex content), deeply nested selectors, and third-party library overrides are easier to keep as CSS.

The bridge: @apply lets you use Tailwind utilities inside regular CSS rules:

/* Keep this in your CSS file */
.markdown-content h2::after {
  content: '';
  @apply block w-12 h-0.5 bg-blue-500 mt-2;
}

/* Third-party override */
.react-datepicker__header {
  @apply bg-white border-b border-gray-200 p-4;
}

Use @apply as a temporary bridge during migration, not a permanent pattern. If you find yourself writing @apply for everything, you're not actually migrating to Tailwind --- you're just using Tailwind as a verbose Sass replacement. The goal is inline utilities in your HTML. Reserve @apply for cases where inline classes genuinely can't work.

The Gotchas That Will Bite You

Specificity conflicts. Your old CSS classes have specificity. Tailwind utilities also have specificity. When both target the same element, the old class might win because it was loaded later or has a more specific selector. Fix this by adding !important to Tailwind utilities (enabled in config with important: true) or by deleting the old CSS rule before adding the Tailwind replacement.

Hover/focus state duplication. If your old CSS has .button:hover { background: #1d4ed8; } and you add Tailwind's hover:bg-blue-700 to the same element, you get two hover styles competing. Always remove the old CSS rule when adding the Tailwind replacement. Don't leave both.

Custom properties (CSS variables). If your old CSS uses var(--primary-color) extensively, you can reference them in Tailwind using arbitrary values: bg-[var(--primary-color)]. But a cleaner approach is to map those variables to Tailwind's config and drop the CSS variables entirely.

Print styles. Tailwind has a print: variant for print-specific styles, but if your old CSS has a complex @media print block, it's usually easier to keep it as vanilla CSS than to convert 30+ print-specific rules to inline utilities.

Transition and animation properties. Tailwind handles simple transitions (transition, duration-300, ease-in-out) and basic animations (animate-spin, animate-pulse). Complex multi-step @keyframes should stay as vanilla CSS.

Migration Timeline: What to Expect

Based on 3 migrations I've done (ranging from 3,000 to 14,000 lines of CSS):

Codebase SizeEstimated TimeDaily Pace
Under 2,000 lines3-5 days3-5 components/day
2,000-5,000 lines1-2 weeks2-3 components/day
5,000-15,000 lines2-4 weeks1-2 components/day
Over 15,000 lines4-8 weeks1 component/day + testing
The pace isn't limited by conversion speed --- it's limited by testing. Every converted component needs to be checked across breakpoints, hover/focus states, and dark mode (if applicable). Rushing the migration and skipping visual QA is how you end up with a "completed" migration that has 50 subtle UI bugs.

For unit conversions during migration --- figuring out what 18px is in rem or what spacing value maps to what Tailwind class --- the CSS unit converter saves time. Paste a pixel value, get the rem equivalent and the closest Tailwind class.

FAQ

Should I migrate everything or just new code?

If the project is actively maintained and growing, migrate incrementally --- new components in Tailwind, old components converted as you touch them. If the project is in maintenance mode with rare changes, don't migrate at all. The cost of migration only pays off if you're actively writing new CSS. Converting 5,000 lines of CSS that nobody touches again is busywork.

Can I use Tailwind with Sass or Less?

Yes, but it's redundant. Tailwind replaces most of what Sass provides: variables become theme config, nesting becomes direct utility classes, mixins become component composition. During migration, Tailwind runs alongside Sass fine --- PostCSS processes both. After migration, you can usually drop Sass entirely.

What about CSS modules in React/Next.js?

CSS modules scope styles to components, which solves the same naming collision problem Tailwind solves with utility classes. You can use both simultaneously during migration. Convert CSS module files one at a time: move the styles from the .module.css file to Tailwind utilities on the JSX elements, then delete the module file.

How do I handle global styles like resets and base typography?

Keep them. Tailwind's @layer base is designed for global styles. Move your CSS reset and base typography rules into a Tailwind base layer or keep them in a separate globals.css file. Don't try to convert * { box-sizing: border-box; } or body { font-family: ... } into utility classes --- that's not what utilities are for.

Next Steps