सामग्री पर जाएं
·23 मिनट पढ़ने का समय

Tailwind CSS v4: वास्तव में क्या बदला और Migrate करना चाहिए या नहीं

CSS-first configuration, @layer integration, built-in container queries, नए engine की performance, breaking changes, और v3 से v4 migration का मेरा ईमानदार अनुभव।

साझा करें:X / TwitterLinkedIn

मैं Tailwind CSS v1.x से इस्तेमाल कर रहा हूं, जब आधी community को लगता था कि यह एक abomination है और बाकी आधी बिना रुके इससे ship कर रही थी। हर major version एक significant leap रहा है, लेकिन v4 अलग है। यह सिर्फ एक feature release नहीं है। यह एक ground-up architectural rewrite है जो आपके और framework के बीच के fundamental contract को बदल देती है।

दो production projects को v3 से v4 पर migrate करने और तीन नए projects को v4 पर scratch से शुरू करने के बाद, मेरे पास एक clear picture है कि genuinely क्या better है, क्या rough है, और आज migrate करना चाहिए या नहीं। कोई hype नहीं, कोई outrage नहीं — बस वो जो मैंने observe किया।

बड़ी तस्वीर: v4 वास्तव में क्या है#

Tailwind CSS v4 एक साथ तीन चीज़ें है:

  1. एक नया engine — JavaScript से Rust में rewrite (Oxide engine), जो builds को dramatically faster बनाता है
  2. एक नया configuration paradigm — CSS-first configuration default के रूप में tailwind.config.js की जगह ले लेती है
  3. CSS platform के साथ tighter integration — native @layer, container queries, @starting-style, और cascade layers first-class citizens हैं

Headline हर जगह "10x faster" दिखेगी। यह real है, लेकिन actual change को undersell करती है। Tailwind को configure और extend करने का mental model fundamentally shift हो गया है। अब आप CSS के साथ काम कर रहे हैं, उस JavaScript configuration object के साथ नहीं जो CSS generate करता था।

यहां एक minimal Tailwind v4 setup कैसा दिखता है:

css
/* app.css — यह पूरा setup है */
@import "tailwindcss";

बस इतना। कोई config file नहीं। कोई PostCSS plugin configuration नहीं (ज़्यादातर setups के लिए)। कोई @tailwind base; @tailwind components; @tailwind utilities; directives नहीं। एक import, और आप चल रहे हो।

v3 से तुलना करें:

css
/* v3 — app.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
js
// v3 — tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};
js
// v3 — postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

तीन files एक line में reduce हो गईं। यह सिर्फ कम boilerplate नहीं है — यह misconfiguration के लिए कम surface area है। v4 में content detection automatic है। यह आपकी project files को बिना glob patterns spell out किए scan करता है।

@theme के साथ CSS-First Configuration#

यह सबसे बड़ा conceptual shift है। v3 में, आप Tailwind को एक JavaScript config object के ज़रिए customize करते थे:

js
// v3 — tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          50: "#eff6ff",
          500: "#3b82f6",
          900: "#1e3a5f",
        },
      },
      fontFamily: {
        display: ["Inter Variable", "sans-serif"],
      },
      spacing: {
        18: "4.5rem",
        112: "28rem",
      },
      borderRadius: {
        "4xl": "2rem",
      },
    },
  },
};

v4 में, यह सब @theme directive का इस्तेमाल करके CSS में रहता है:

css
@import "tailwindcss";
 
@theme {
  --color-brand-50: #eff6ff;
  --color-brand-500: #3b82f6;
  --color-brand-900: #1e3a5f;
 
  --font-display: "Inter Variable", sans-serif;
 
  --spacing-18: 4.5rem;
  --spacing-112: 28rem;
 
  --radius-4xl: 2rem;
}

पहले, मैंने इसका resist किया। मुझे एक single JavaScript object में अपना पूरा design system देखना पसंद था। लेकिन CSS approach के साथ एक हफ्ते बाद, मैंने तीन कारणों से अपनी राय बदल ली:

1. Native CSS custom properties automatically expose होती हैं। @theme में define की गई हर value :root पर CSS custom property बन जाती है। इसका मतलब है कि आपकी theme values plain CSS में, CSS Modules में, <style> tags में, कहीं भी जहां CSS चलती है, accessible हैं:

css
/* यह आपको free में मिलता है */
:root {
  --color-brand-50: #eff6ff;
  --color-brand-500: #3b82f6;
  --color-brand-900: #1e3a5f;
}
css
/* इन्हें कहीं भी इस्तेमाल करें — Tailwind की ज़रूरत नहीं */
.custom-element {
  border: 2px solid var(--color-brand-500);
}

2. आप @theme के अंदर CSS features इस्तेमाल कर सकते हैं। Media queries, light-dark(), calc() — real CSS यहां काम करती है क्योंकि यह real CSS है:

css
@theme {
  --color-surface: light-dark(#ffffff, #0a0a0a);
  --color-text: light-dark(#0a0a0a, #fafafa);
  --spacing-container: calc(100vw - 2rem);
}

3. आपकी बाकी CSS के साथ co-location। आपकी theme, आपकी custom utilities, और आपकी base styles सब एक ही language में, चाहें तो एक ही file में रहती हैं। "CSS world" और "JavaScript config world" के बीच कोई context switching नहीं।

Default Theme को Override करना vs Extend करना#

v3 में आपके पास theme (replace) vs theme.extend (merge) था। v4 में, mental model अलग है:

css
@import "tailwindcss";
 
/* यह default theme को EXTEND करता है — मौजूदा colors के साथ brand colors जोड़ता है */
@theme {
  --color-brand-500: #3b82f6;
}

अगर आप एक namespace को पूरी तरह replace करना चाहते हैं (जैसे सभी default colors हटाना), तो आप @theme के साथ --color-* wildcard reset इस्तेमाल करते हैं:

css
@import "tailwindcss";
 
@theme {
  /* पहले सभी default colors clear करें */
  --color-*: initial;
 
  /* अब सिर्फ अपने colors define करें */
  --color-white: #ffffff;
  --color-black: #000000;
  --color-brand-50: #eff6ff;
  --color-brand-500: #3b82f6;
  --color-brand-900: #1e3a5f;
}

यह wildcard reset pattern elegant है। आप exactly चुनते हैं कि default theme के कौन से parts रखने हैं और कौन से replace करने हैं। सभी default spacing चाहिए लेकिन custom colors? --color-*: initial; reset करें और spacing को छोड़ दें।

Multiple Theme Files#

बड़े projects के लिए, आप अपनी theme को files में split कर सकते हैं:

css
/* styles/theme/colors.css */
@theme {
  --color-brand-50: #eff6ff;
  --color-brand-100: #dbeafe;
  --color-brand-200: #bfdbfe;
  --color-brand-300: #93c5fd;
  --color-brand-400: #60a5fa;
  --color-brand-500: #3b82f6;
  --color-brand-600: #2563eb;
  --color-brand-700: #1d4ed8;
  --color-brand-800: #1e40af;
  --color-brand-900: #1e3a5f;
  --color-brand-950: #172554;
}
 
/* styles/theme/typography.css */
@theme {
  --font-display: "Inter Variable", sans-serif;
  --font-body: "Source Sans 3 Variable", sans-serif;
  --font-mono: "JetBrains Mono Variable", monospace;
 
  --text-display: 3.5rem;
  --text-display--line-height: 1.1;
  --text-display--letter-spacing: -0.02em;
}
css
/* app.css */
@import "tailwindcss";
@import "./theme/colors.css";
@import "./theme/typography.css";

यह v3 pattern से बहुत cleaner है जहां एक giant tailwind.config.js होता था या इसे require() से split करने की कोशिश करते थे।

Oxide Engine: यह वास्तव में 10x Faster है#

Tailwind v4 का engine Rust में एक complete rewrite है। वे इसे Oxide कहते हैं। मुझे "10x faster" claim पर शक था — marketing numbers rarely real projects के संपर्क में survive करते हैं। तो मैंने benchmark किया।

मेरा test project: एक Next.js app जिसमें 847 components, 142 pages, लगभग 23,000 Tailwind class usages।

Metricv3 (Node)v4 (Oxide)Improvement
Initial build4,280ms387ms11x
Incremental (1 file edit)340ms18ms19x
Full rebuild (clean)5,100ms510ms10x
Dev server start3,200ms290ms11x

"10x" claim मेरे project के लिए conservative है। Incremental builds वो हैं जहां यह वास्तव में चमकता है — 18ms का मतलब है कि यह essentially instant है। आप file save करते हैं और tabs switch करने से पहले browser में new styles आ जाती हैं।

यह इतना Faster क्यों है?#

तीन कारण:

1. JavaScript की बजाय Rust। Core CSS parser, class detection, और code generation सब native Rust हैं। यह "मज़े के लिए Rust में rewrite करते हैं" वाली situation नहीं है — CSS parsing genuinely CPU-bound काम है जहां native code को V8 पर massive advantage है।

2. Hot path में कोई PostCSS नहीं। v3 में, Tailwind एक PostCSS plugin था। हर build का मतलब था: CSS को PostCSS AST में parse करो, Tailwind plugin चलाओ, वापस CSS string में serialize करो, फिर बाकी PostCSS plugins चलें। v4 में, Tailwind का अपना CSS parser है जो directly source से output तक जाता है। PostCSS compatibility के लिए अभी भी supported है, लेकिन primary path इसे पूरी तरह skip करता है।

3. Smarter incremental processing। नया engine aggressively cache करता है। जब आप एक file edit करते हैं, तो यह सिर्फ उस file को class names के लिए re-scan करता है और सिर्फ बदली हुई CSS rules regenerate करता है। v3 engine इसमें उतना बुरा नहीं था जितना लोग मानते हैं (JIT mode पहले से incremental था), लेकिन v4 fine-grained dependency tracking के साथ इसे बहुत आगे ले जाता है।

क्या Speed वास्तव में Matter करती है?#

हां, लेकिन उस कारण से नहीं जो आप सोचते हैं। ज़्यादातर projects के लिए, v3 की build speed "ठीक" थी। आप dev में कुछ सौ milliseconds wait करते। दर्दनाक नहीं।

v4 की speed इसलिए matter करती है क्योंकि यह Tailwind को आपकी toolchain में invisible बना देती है। जब builds 20ms से कम हों, तो आप Tailwind के बारे में build step के रूप में सोचना बंद कर देते हैं। यह syntax highlighting जैसा हो जाता है — हमेशा वहां, कभी रास्ते में नहीं। यह psychological difference पूरे दिन की development में significant है।

Native @layer Integration#

v3 में, Tailwind अपना खुद का layer system इस्तेमाल करता था @layer base, @layer components, और @layer utilities के साथ। ये CSS cascade layers जैसी दिखती थीं लेकिन थीं नहीं — ये Tailwind-specific directives थीं जो control करती थीं कि generated CSS output में कहां appear हो।

v4 में, Tailwind actual CSS cascade layers इस्तेमाल करता है:

css
/* v4 output — simplified */
@layer theme, base, components, utilities;
 
@layer base {
  /* reset, preflight */
}
 
@layer components {
  /* आपकी component classes */
}
 
@layer utilities {
  /* सभी generated utility classes */
}

यह एक significant change है क्योंकि CSS cascade layers की real specificity implications हैं। एक lower-priority layer में rule हमेशा higher-priority layer में rule से हारता है, selector specificity की परवाह किए बिना। इसका मतलब:

css
@layer components {
  /* specificity: 0-1-0 */
  .card { padding: 1rem; }
}
 
@layer utilities {
  /* specificity: 0-1-0 — same specificity लेकिन जीतता है क्योंकि utilities layer बाद में है */
  .p-4 { padding: 1rem; }
}

Utilities हमेशा components को override करती हैं। Components हमेशा base को override करते हैं। यह वैसा ही है जैसे Tailwind v3 में conceptually काम करता था, लेकिन अब इसे browser का cascade layer mechanism enforce करता है, source order manipulation नहीं।

Custom Utilities जोड़ना#

v3 में, आप custom utilities एक plugin API या @layer utilities से define करते थे:

js
// v3 — plugin approach
const plugin = require("tailwindcss/plugin");
 
module.exports = {
  plugins: [
    plugin(function ({ addUtilities }) {
      addUtilities({
        ".text-balance": {
          "text-wrap": "balance",
        },
        ".text-pretty": {
          "text-wrap": "pretty",
        },
      });
    }),
  ],
};

v4 में, custom utilities @utility directive से define होती हैं:

css
@import "tailwindcss";
 
@utility text-balance {
  text-wrap: balance;
}
 
@utility text-pretty {
  text-wrap: pretty;
}

@utility directive Tailwind को बताती है "यह एक utility class है — इसे utilities layer में रखो और इसे variants के साथ इस्तेमाल करने दो।" वो last part key है। @utility से define की गई utility automatically hover:, focus:, md:, और हर दूसरे variant के साथ काम करती है:

html
<p class="text-pretty md:text-balance">...</p>

Custom Variants#

आप @variant से custom variants भी define कर सकते हैं:

css
@import "tailwindcss";
 
@variant hocus (&:hover, &:focus);
@variant theme-dark (.dark &);
html
<button class="hocus:bg-brand-500 theme-dark:text-white">
  Click me
</button>

यह ज़्यादातर use cases के लिए v3 की addVariant plugin API की जगह लेता है। यह कम powerful है (आप programmatic variant generation नहीं कर सकते), लेकिन 90% जो लोग actually करते हैं वो cover करता है।

Container Queries: Built-In, कोई Plugin नहीं#

Container queries v3 में सबसे ज़्यादा requested features में से एक थीं। आप उन्हें @tailwindcss/container-queries plugin से पा सकते थे, लेकिन यह एक add-on था। v4 में, वे framework में built-in हैं।

Basic Usage#

Parent को @container से mark करें और इसके size को @ prefix से query करें:

html
<!-- parent को container mark करें -->
<div class="@container">
  <!-- parent की width के responsive, viewport के नहीं -->
  <div class="flex flex-col @md:flex-row @lg:grid @lg:grid-cols-3">
    <div class="p-4">Card 1</div>
    <div class="p-4">Card 2</div>
    <div class="p-4">Card 3</div>
  </div>
</div>

@md, @lg, आदि variants responsive breakpoints की तरह काम करते हैं लेकिन viewport की बजाय nearest @container ancestor के relative हैं। Breakpoint values Tailwind के default breakpoints से correspond करती हैं:

VariantMin-width
@sm24rem (384px)
@md28rem (448px)
@lg32rem (512px)
@xl36rem (576px)
@2xl42rem (672px)

Named Containers#

आप specific ancestors को query करने के लिए containers को name कर सकते हैं:

html
<div class="@container/sidebar">
  <div class="@container/card">
    <!-- card container को query करता है -->
    <div class="@md/card:text-lg">...</div>
 
    <!-- sidebar container को query करता है -->
    <div class="@lg/sidebar:hidden">...</div>
  </div>
</div>

यह क्यों Matter करता है#

Container queries responsive design के बारे में आपकी सोच बदल देती हैं। "इस viewport width पर, तीन columns दिखाओ" की बजाय, आप कहते हैं "जब इस component का container काफी wide हो, तीन columns दिखाओ।" Components वास्तव में self-contained बन जाते हैं। आप एक card component को full-width layout से sidebar में move कर सकते हैं और यह automatically adapt हो जाता है। कोई media query gymnastics नहीं।

मैं अपनी component libraries को viewport breakpoints की बजाय default रूप से container queries इस्तेमाल करने के लिए refactor कर रहा हूं। Result ऐसे components हैं जो आप उन्हें कहीं भी रखें काम करते हैं, बिना parent को component के responsive behavior के बारे में कुछ जानने की ज़रूरत।

html
<!-- यह component किसी भी container में adapt होता है -->
<article class="@container">
  <div class="grid grid-cols-1 @md:grid-cols-[200px_1fr] gap-4">
    <img
      class="w-full @md:w-auto rounded-lg aspect-video @md:aspect-square object-cover"
      src="/post-image.jpg"
      alt=""
    />
    <div>
      <h2 class="text-lg @lg:text-xl font-semibold">Post Title</h2>
      <p class="mt-2 text-sm @md:text-base text-gray-600">
        Post excerpt goes here...
      </p>
      <div class="mt-4 hidden @md:flex gap-2">
        <span class="text-xs bg-gray-100 px-2 py-1 rounded">Tag</span>
      </div>
    </div>
  </div>
</article>

नए Variants जो वास्तव में Matter करते हैं#

v4 कई नए variants जोड़ता है जिन्हें मैं लगातार इस्तेमाल कर रहा हूं। ये real gaps भरते हैं।

starting: Variant#

यह CSS @starting-style पर map होता है, जो आपको किसी element की initial state define करने देता है जब वह पहली बार appear होता है। यह JavaScript बिना element entry animate करने का missing piece है:

html
<dialog class="opacity-0 starting:opacity-0 open:opacity-100 transition-opacity duration-300">
  <p>यह dialog open होने पर fade in होता है</p>
</dialog>

starting: variant @starting-style block के अंदर CSS generate करता है:

css
/* Tailwind यह generate करता है */
@starting-style {
  dialog[open] {
    opacity: 0;
  }
}
 
dialog[open] {
  opacity: 1;
  transition: opacity 300ms;
}

यह dialogs, popovers, dropdown menus — कुछ भी जिसे entry animation चाहिए — के लिए बहुत बड़ी बात है। इससे पहले, आपको next frame पर class add करने के लिए JavaScript चाहिए था, या आप @keyframes इस्तेमाल करते थे। अब यह एक utility class है।

not-* Variant#

Negation। कुछ जो हम हमेशा से चाहते थे:

html
<!-- last के अलावा हर child को border मिलता है -->
<div class="divide-y">
  <div class="not-last:pb-4">Item 1</div>
  <div class="not-last:pb-4">Item 2</div>
  <div class="not-last:pb-4">Item 3</div>
</div>
 
<!-- disabled नहीं है वो सब style करो -->
<input class="not-disabled:hover:border-brand-500" />
 
<!-- data attributes negate करो -->
<div class="not-data-active:opacity-50">...</div>

nth-* Variants#

Direct nth-child और nth-of-type access:

html
<ul>
  <li class="nth-1:font-bold">पहला item — bold</li>
  <li class="nth-even:bg-gray-50">Even rows — gray bg</li>
  <li class="nth-odd:bg-white">Odd rows — white bg</li>
  <li class="nth-[3n+1]:text-brand-500">हर तीसरा+1 — brand color</li>
</ul>

Bracket syntax (nth-[3n+1]) किसी भी valid nth-child expression support करता है। यह बहुत सारी custom CSS replace करता है जो मैं table striping और grid patterns के लिए लिखता था।

in-* Variant (Parent State)#

यह group-* का inverse है। "जब मेरा parent (group) hovered हो, मुझे style करो" की बजाय, यह है "जब मैं किसी ऐसे parent के अंदर हूं जो इस state से match करता है, मुझे style करो":

html
<div class="in-data-active:bg-brand-50">
  जब किसी भी ancestor के पास data-active हो तो इसे background मिलता है
</div>

**: Deep Universal Variant#

सभी descendants को style करो, सिर्फ direct children को नहीं। यह controlled power है — इसे sparingly इस्तेमाल करो, लेकिन prose content और CMS output के लिए invaluable है:

html
<!-- इस div के अंदर सभी paragraphs, किसी भी depth पर -->
<div class="**:data-highlight:bg-yellow-100">
  <section>
    <p data-highlight>यह highlighted हो जाता है</p>
    <div>
      <p data-highlight>यह भी, deeper नesting में</p>
    </div>
  </section>
</div>

Breaking Changes: वास्तव में क्या Broke हुआ#

सीधे बात करता हूं। अगर आपके पास बड़ा v3 project है, तो migration trivial नहीं है। यहां मेरे projects में क्या broke हुआ:

1. Configuration Format#

आपकी tailwind.config.js out of the box काम नहीं करेगी। आपको या तो:

  • इसे @theme CSS में convert करना होगा (नई architecture के लिए recommended)
  • Compatibility layer @config directive इस्तेमाल करनी होगी (quick migration path)
css
/* quick migration — अपनी पुरानी config रखें */
@import "tailwindcss";
@config "../../tailwind.config.js";

यह @config bridge काम करता है, लेकिन यह explicitly एक migration tool है। Recommendation है कि समय के साथ @theme पर move करें।

2. हटाई गई Deprecated Utilities#

कुछ utilities जो v3 में deprecated थीं, अब चली गई हैं:

/* v4 में REMOVED */
bg-opacity-*     → bg-black/50 (slash opacity syntax) इस्तेमाल करें
text-opacity-*   → text-black/50 इस्तेमाल करें
border-opacity-* → border-black/50 इस्तेमाल करें
flex-shrink-*    → shrink-* इस्तेमाल करें
flex-grow-*      → grow-* इस्तेमाल करें
overflow-ellipsis → text-ellipsis इस्तेमाल करें
decoration-slice  → box-decoration-slice इस्तेमाल करें
decoration-clone  → box-decoration-clone इस्तेमाल करें

अगर आप v3 में पहले से modern syntax इस्तेमाल कर रहे थे (slash opacity, shrink-*), तो आप ठीक हैं। अगर नहीं, तो ये straightforward find-and-replace changes हैं।

3. Default Color Palette Changes#

Default color palette थोड़ा shift हुआ। अगर आप v3 से exact color values पर depend करते हैं (name से नहीं बल्कि actual hex value से), तो आपको visual differences दिख सकते हैं। Named colors (blue-500, gray-200) अभी भी exist करते हैं लेकिन कुछ hex values बदल गए।

4. Content Detection#

v3 को explicit content configuration चाहिए था:

js
// v3
module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
};

v4 automatic content detection इस्तेमाल करता है। यह आपकी project root scan करता है और template files automatically ढूंढता है। यह ज़्यादातर "बस काम करता है," लेकिन अगर आपकी project structure unusual है (project root के बाहर packages वाला monorepo, unexpected locations में template files), तो आपको source paths explicitly configure करनी पड़ सकती हैं:

css
@import "tailwindcss";
@source "../shared-components/**/*.tsx";
@source "../design-system/src/**/*.tsx";

5. Plugin API Changes#

अगर आपने custom plugins लिखे हैं, तो API बदल गई। addUtilities, addComponents, addBase, और addVariant functions compatibility layer के ज़रिए अभी भी काम करते हैं, लेकिन idiomatic v4 approach CSS-native है:

js
// v3 plugin
plugin(function ({ addUtilities, theme }) {
  addUtilities({
    ".scrollbar-hide": {
      "-ms-overflow-style": "none",
      "scrollbar-width": "none",
      "&::-webkit-scrollbar": {
        display: "none",
      },
    },
  });
});
css
/* v4 — बस CSS */
@utility scrollbar-hide {
  -ms-overflow-style: none;
  scrollbar-width: none;
  &::-webkit-scrollbar {
    display: none;
  }
}

ज़्यादातर first-party plugins (@tailwindcss/typography, @tailwindcss/forms, @tailwindcss/aspect-ratio) के v4-compatible versions हैं। Third-party plugins hit or miss हैं — migrate करने से पहले उनकी repo check करें।

6. JIT ही एकमात्र Mode है#

v3 में, आप JIT mode से opt out कर सकते थे (हालांकि लगभग कोई करता नहीं था)। v4 में, कोई non-JIT mode नहीं है। सब कुछ on-demand generate होता है, हमेशा। अगर आपके पास पुराना AOT (ahead-of-time) engine इस्तेमाल करने का कोई कारण था, तो वो path अब नहीं है।

7. कुछ Variant Syntax Changes#

कुछ variants renamed हुए या behavior बदला:

html
<!-- v3 -->
<div class="[&>*]:p-4">...</div>
 
<!-- v4 — >* part अब inset variant syntax इस्तेमाल करता है -->
<div class="*:p-4">...</div>

Arbitrary variant syntax [&...] अभी भी काम करता है, लेकिन v4 common patterns के लिए named alternatives provide करता है।

Migration Guide: असली Process#

यहां मैंने वास्तव में कैसे migrate किया, docs की happy path नहीं बल्कि process वास्तव में कैसा दिखता था।

Step 1: Official Codemod चलाओ#

Tailwind एक codemod provide करता है जो ज़्यादातर mechanical changes handle करता है:

bash
npx @tailwindcss/upgrade

यह बहुत कुछ automatically करता है:

  • @tailwind directives को @import "tailwindcss" में convert करता है
  • Deprecated utility classes rename करता है
  • Variant syntax update करता है
  • Opacity utilities को slash syntax में migrate करता है (bg-opacity-50 को bg-black/50)
  • आपकी config से basic @theme block बनाता है

Codemod क्या अच्छे से Handle करता है#

  • Utility class renames (लगभग perfect)
  • Directive syntax changes
  • Simple theme values (colors, spacing, fonts)
  • Opacity syntax migration

Codemod क्या Handle नहीं करता#

  • Complex plugin conversions
  • Dynamic config values (JavaScript में theme() calls)
  • Conditional theme configuration (जैसे environment पर based theme values)
  • Custom plugin API migrations
  • Arbitrary value edge cases जहां new parser अलग interpret करता है
  • JavaScript में dynamically construct किए गए class names (template literals, string concatenation)

Step 2: PostCSS Configuration Fix करो#

ज़्यादातर setups के लिए, आप अपनी PostCSS config update करेंगे:

js
// postcss.config.js — v4
module.exports = {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

Note: plugin name tailwindcss से @tailwindcss/postcss में बदल गया। अगर आप Vite इस्तेमाल कर रहे हैं, तो PostCSS पूरी तरह skip कर सकते हैं और Vite plugin इस्तेमाल कर सकते हैं:

js
// vite.config.ts
import tailwindcss from "@tailwindcss/vite";
 
export default defineConfig({
  plugins: [tailwindcss()],
});

Step 3: Theme Configuration Convert करो#

यह manual part है। अपनी tailwind.config.js की theme values लें और उन्हें @theme में convert करें:

js
// v3 config — before
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          light: "#60a5fa",
          DEFAULT: "#3b82f6",
          dark: "#1d4ed8",
        },
      },
      fontSize: {
        "2xs": ["0.65rem", { lineHeight: "1rem" }],
      },
      animation: {
        "fade-in": "fade-in 0.5s ease-out",
      },
      keyframes: {
        "fade-in": {
          "0%": { opacity: "0" },
          "100%": { opacity: "1" },
        },
      },
    },
  },
};
css
/* v4 CSS — after */
@import "tailwindcss";
 
@theme {
  --color-brand-light: #60a5fa;
  --color-brand: #3b82f6;
  --color-brand-dark: #1d4ed8;
 
  --text-2xs: 0.65rem;
  --text-2xs--line-height: 1rem;
 
  --animate-fade-in: fade-in 0.5s ease-out;
}
 
@keyframes fade-in {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

ध्यान दें कि keyframes @theme से बाहर निकलते हैं और regular CSS @keyframes बन जाते हैं। @theme में animation name बस उन्हें reference करता है। यह cleaner है — keyframes CSS हैं, उन्हें CSS के रूप में लिखा जाना चाहिए।

Step 4: Visual Regression Testing#

यह non-negotiable है। Migration के बाद, मैंने अपनी app का हर page खोला और visually check किया। मैंने अपने Playwright screenshot tests भी चलाए (अगर आपके पास हैं)। Codemod अच्छा है लेकिन perfect नहीं। Visual review में मुझे जो मिला:

  • कुछ जगहों पर opacity syntax migration ने slightly different results दिए
  • Custom plugin output जो carry over नहीं हुआ
  • Layer ordering के कारण z-index stacking changes
  • कुछ !important overrides जो cascade layers के साथ differently behave करते थे

Step 5: Third-Party Dependencies Update करो#

हर Tailwind-related package check करो:

json
{
  "@tailwindcss/typography": "^1.0.0",
  "@tailwindcss/forms": "^1.0.0",
  "@tailwindcss/container-queries": "REMOVE — अब built-in है",
  "tailwindcss-animate": "v4 support check करें",
  "prettier-plugin-tailwindcss": "latest पर update करें"
}

@tailwindcss/container-queries plugin अब ज़रूरी नहीं है — container queries built-in हैं। बाकी plugins को उनके v4-compatible versions चाहिए।

Next.js के साथ काम करना#

चूंकि मैं ज़्यादातर projects के लिए Next.js इस्तेमाल करता हूं, यहां specific setup है।

Next.js internally PostCSS इस्तेमाल करता है, तो PostCSS plugin natural fit है:

bash
npm install tailwindcss @tailwindcss/postcss
js
// postcss.config.mjs
export default {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};
css
/* app/globals.css */
@import "tailwindcss";
 
@theme {
  --font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "JetBrains Mono Variable", ui-monospace, monospace;
}

यह complete setup है। कोई tailwind.config.js नहीं, कोई autoprefixer नहीं (v4 vendor prefixes internally handle करता है)।

CSS Import Order#

एक चीज़ जिसने मुझे trip किया: v4 में CSS import order ज़्यादा matter करता है cascade layers की वजह से। आपका @import "tailwindcss" आपकी custom styles से पहले आना चाहिए:

css
/* सही order */
@import "tailwindcss";
@import "./theme.css";
@import "./custom-utilities.css";
 
/* आपके inline @theme, @utility, आदि */

अगर आप Tailwind से पहले custom CSS import करते हैं, तो आपकी styles एक lower cascade layer में जा सकती हैं और unexpectedly override हो सकती हैं।

Dark Mode#

Dark mode conceptually वैसे ही काम करता है लेकिन configuration CSS में move हो गई:

css
@import "tailwindcss";
 
/* Class-based dark mode इस्तेमाल करें (default media-based है) */
@variant dark (&:where(.dark, .dark *));

यह v3 config replace करता है:

js
// v3
module.exports = {
  darkMode: "class",
};

@variant approach ज़्यादा flexible है। आप dark mode जैसे चाहें define कर सकते हैं — class-based, data-attribute-based, या media-query-based:

css
/* data attribute approach */
@variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
 
/* media query — यह default है, तो आपको declare करने की ज़रूरत नहीं */
@variant dark (@media (prefers-color-scheme: dark));

Turbopack Compatibility#

अगर आप Next.js को Turbopack (जो अब default dev bundler है) के साथ इस्तेमाल कर रहे हैं, तो v4 बहुत अच्छा काम करता है। Rust engine Turbopack की अपनी Rust-based architecture के साथ अच्छे से mesh होता है। मैंने dev startup times measure किए:

Setupv3 + Webpackv3 + Turbopackv4 + Turbopack
Cold start4.8s2.1s1.3s
HMR (CSS change)450ms180ms40ms

CSS changes के लिए 40ms HMR barely perceptible है। यह instant feel होता है।

Performance Deep Dive: Build Speed से आगे#

Oxide engine के benefits raw build speed से आगे जाते हैं।

Memory Usage#

v4 significantly कम memory इस्तेमाल करता है। मेरे 847-component project पर:

Metricv3v4
Peak memory (build)380MB45MB
Steady-state (dev)210MB28MB

यह CI/CD pipelines के लिए matter करता है जहां memory constrained है, और development machines के लिए जो एक साथ दस processes चला रही होती हैं।

CSS Output Size#

v4 slightly छोटा CSS output generate करता है क्योंकि new engine deduplication और dead code elimination में better है:

v3 output: 34.2 KB (gzipped)
v4 output: 29.8 KB (gzipped)

बिना कोई code बदले 13% reduction। Transformative नहीं, लेकिन free performance।

Theme Values की Tree Shaking#

v4 में, अगर आप एक theme value define करते हैं लेकिन कभी अपने templates में इस्तेमाल नहीं करते, तो corresponding CSS custom property फिर भी emit होती है (यह @theme में है, जो :root variables में map होता है)। हालांकि, unused values के लिए utility classes generate नहीं होतीं। यह v3 के JIT behavior जैसा ही है लेकिन ध्यान देने योग्य है: आपकी CSS custom properties हमेशा available हैं, चाहे किसी utility usage न हो।

अगर आप certain theme values को CSS custom properties generate करने से रोकना चाहते हैं, तो @theme inline इस्तेमाल कर सकते हैं:

css
@theme inline {
  /* ये values utilities generate करती हैं लेकिन CSS custom properties नहीं */
  --color-internal-debug: #ff00ff;
  --spacing-magic-number: 3.7rem;
}

यह internal design tokens के लिए useful है जिन्हें आप CSS variables के रूप में expose नहीं करना चाहते।

Advanced: Multi-Brand के लिए Themes Compose करना#

एक pattern जो v4 significantly आसान बनाता है वो है multi-brand theming। क्योंकि theme values CSS custom properties हैं, आप उन्हें runtime पर swap कर सकते हैं:

css
@import "tailwindcss";
 
@theme {
  --color-brand: var(--brand-primary, #3b82f6);
  --color-brand-light: var(--brand-light, #60a5fa);
  --color-brand-dark: var(--brand-dark, #1d4ed8);
}
 
/* Brand overrides */
.theme-acme {
  --brand-primary: #e11d48;
  --brand-light: #fb7185;
  --brand-dark: #9f1239;
}
 
.theme-globex {
  --brand-primary: #059669;
  --brand-light: #34d399;
  --brand-dark: #047857;
}
html
<body class="theme-acme">
  <!-- सभी bg-brand, text-brand, आदि Acme colors इस्तेमाल करते हैं -->
  <div class="bg-brand text-white">Acme Corp</div>
</body>

v3 में, इसके लिए एक custom plugin या Tailwind के बाहर complex CSS variable setup चाहिए था। v4 में, यह natural है — theme CSS variables है, और CSS variables cascade होती हैं। यह उन चीज़ों में से एक है जो CSS-first approach को सही feel कराती है।

v3 से क्या miss करता हूं#

संतुलित रहते हैं। कुछ चीज़ें हैं जो v3 करता था जिन्हें मैं genuinely v4 में miss करता हूं:

1. Programmatic themes के लिए JavaScript config। मेरे पास एक project था जहां हम config में एक JavaScript function से एक single brand color से color scales generate करते थे। v4 में, आप @theme में ऐसा नहीं कर सकते — आपको या तो एक build step चाहिए जो CSS file generate करे, या colors एक बार compute करके paste करें। @config compatibility layer help करती है, लेकिन यह long-term story नहीं है।

2. Launch पर IntelliSense better था। v3 VS Code extension के पास years की polish थी। v4 IntelliSense काम करती है लेकिन शुरू में कुछ gaps थे — custom @theme values कभी-कभी autocomplete नहीं होती थीं, और @utility definitions हमेशा pick up नहीं होती थीं। यह recent updates के साथ substantially improve हो गया है, लेकिन mention करना ज़रूरी है।

3. Ecosystem maturity। v3 के around ecosystem विशाल था। Headless UI, Radix, shadcn/ui, Flowbite, DaisyUI — सब v3 के against test किए गए थे। v4 support roll out हो रहा है लेकिन universal नहीं है। मुझे एक component library में v4 compatibility fix करने के लिए PR submit करनी पड़ी।

Migrate करना चाहिए?#

v4 के साथ कई हफ्ते रहने के बाद यह मेरा decision framework है:

अभी Migrate करें अगर:#

  • आप नया project शुरू कर रहे हैं (obvious choice — v4 से शुरू करें)
  • आपके project में minimal custom plugins हैं
  • आप बड़े projects के लिए performance benefits चाहते हैं
  • आप पहले से modern Tailwind patterns इस्तेमाल कर रहे हैं (slash opacity, shrink-*, आदि)
  • आपको container queries चाहिए और plugin नहीं जोड़ना चाहते

Wait करें अगर:#

  • आप heavily third-party Tailwind plugins पर depend करते हैं जो v4 support नहीं करते
  • आपके पास complex programmatic theme configuration है
  • आपका project stable है और actively develop नहीं हो रहा (क्यों छेड़ना?)
  • आप feature sprint के बीच में हैं (sprints के बीच migrate करो, दौरान नहीं)

Migrate मत करें अगर:#

  • आप v2 या पहले पर हैं (पहले v3 में upgrade करें, stabilize करें, फिर v4 consider करें)
  • आपका project अगले कुछ महीनों में खत्म हो जाएगा (churn worth नहीं)

मेरी ईमानदार राय#

नए projects के लिए, v4 obvious choice है। CSS-first configuration cleaner है, engine dramatically faster है, और नए features (container queries, @starting-style, नए variants) genuinely useful हैं।

Existing projects के लिए, मैं staged approach recommend करता हूं:

  1. अभी: कोई भी नया project v4 पर start करो
  2. जल्दी: एक छोटा internal project v4 में convert करके experiment करो
  3. जब ready हो: Production projects एक quiet sprint में migrate करो, visual regression testing के साथ

Migration painful नहीं है अगर आप इसके लिए prepare करें। Codemod 80% काम handle करता है। बाकी 20% manual है लेकिन straightforward। एक medium project के लिए एक दिन budget रखो, एक बड़े के लिए दो से तीन दिन।

Tailwind v4 वो है जो Tailwind हमेशा से होनी चाहिए थी। JavaScript configuration हमेशा उस समय की tooling की concession थी। CSS-first configuration, native cascade layers, एक Rust engine — ये trends नहीं हैं, ये framework का platform के साथ catch up करना है। Web platform better हुआ, और Tailwind v4 इसके खिलाफ लड़ने की बजाय इसमें lean करता है।

अपने design tokens CSS में लिखना, उन्हें CSS features से compose करना, और browser के अपने cascade को specificity handle करने देना — यही सही direction है। यहां पहुंचने में चार major versions लगे, लेकिन result Tailwind का अब तक का सबसे coherent version है।

अपना अगला project इस पर start करो। पीछे नहीं देखोगे।

संबंधित पोस्ट