Back to articlesDesign
Tailwind CSS v4: configuration moves into your CSS
No more tailwind.config.js. v4 moves the whole configuration into your stylesheet, with @theme, @plugin and @custom-variant.
#tailwindcss#css#frontend#design
Mike Codeur
August 02, 20264 minDesign
The most visible change in Tailwind CSS v4 is not a new utility class: it is the disappearance of
This is the heart of v4. What you declare in
Declaring
That line says the
This is the right answer for a component that must look good both full-width and inside a narrow sidebar — a case viewport breakpoints have never handled properly.
tailwind.config.js. Configuration now lives inside your CSS, and that changes how you wire a design system into a project.
The new entry point
Where v3 required three directives plus a JavaScript config file, v4 fits on one line:@import 'tailwindcss';A typical v4 project has no
tailwind.config.js at the root at all. If you
still have one after migrating, it usually means something is left to move
into the CSS.@theme: your tokens become utilities
This is the heart of v4. What you declare in @theme does double duty: the CSS variables are exposed at runtime, and Tailwind generates the matching classes.
@import 'tailwindcss';
@theme {
--color-background: var(--background);
--color-foreground: var(--foreground);
}--color-background is enough to get bg-background, text-background and border-background. The token name drives the available utilities: the --color-* prefix produces colour classes, --spacing-* spacing, --font-* font families.
Why it matters on a real project: your design tokens are no longer copied from
a JS file into CSS. There is one declaration, and the classes follow
automatically.
Dark mode driven by a variant
In v4, class-based dark mode is declared with@custom-variant:
@custom-variant dark (&:is(.dark *));dark: prefix applies as soon as an ancestor carries the .dark class. Exactly what you need when the theme is driven by JavaScript (a theme switcher, a stored preference) rather than by the system setting alone.
Combined with @theme, you get a clean system: components never hard-code a colour, only tokens.
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.15 0 0);
}
.dark {
--background: oklch(0.15 0 0);
--foreground: oklch(0.98 0 0);
}<div class="bg-background text-foreground">
This block follows the theme without a single dark: class
</div>The classic trap: writing
bg-white dark:bg-slate-900 everywhere. It works,
but every new colour becomes a double decision to maintain. Go through a token
and dark mode comes for free.Plugins move into CSS too
No more registering them in a config file:@plugin 'tailwindcss-animate';
@plugin '@tailwindcss/typography';OKLCH by default
v4 leans onoklch rather than hex. The benefit is concrete: lightness is perceptually uniform, so a palette generated by varying the first value stays visually consistent — something HSL never quite manages.
@theme {
--color-primary: oklch(0.62 0.19 260);
}Hex still works. There is no need to convert an entire existing palette on
migration day: do it when you next revisit your colours.
Container queries built in
They no longer need a plugin: a component can react to the width of its container rather than the viewport.<div class="@container">
<div class="@sm:text-lg @md:flex @md:gap-6">
Responsive to available space, not to the window
</div>
</div>Migrating from v3
1
Replace the directives
The three
@tailwind lines become a single @import 'tailwindcss'.2
Move the theme
Whatever lived in
theme.extend in the JS file goes into @theme as CSS
variables.3
Declare the plugins
Each plugin becomes one
@plugin line in the CSS.4
Redeclare dark mode
The
darkMode: 'class' option becomes a @custom-variant line.5
Delete the config file
Once everything has moved,
tailwind.config.js has no reason to exist.| What lived in v3 | Where it goes in v4 |
|---|---|
theme.extend.colors | @theme { --color-* } |
theme.extend.spacing | @theme { --spacing-* } |
plugins: [...] | @plugin '...' |
darkMode: 'class' | @custom-variant dark (...) |
content: [...] | Automatic detection |
Do not migrate halfway. A config split between JS and CSS produces missing
utilities with no clear error message — the kind of problem that costs an hour
of debugging for one forgotten line.
Takeaway
v4 does more than relocate a file: it removes the boundary between "configuring Tailwind" and "writing CSS". A design system is declared once, in a stylesheet, and feeds both the CSS variables and the utility classes. For a project meant to last, that is one less source of truth to keep in sync.Written by
MC
Mike Codeur
Author
Related Articles
DevelopmentIntroduction to Next.js 16
What actually changes when you build with Next.js 16 and React 19: async params, Server Components, Server Actions and Turbopack.
Aug 03, 20265 min
Read more
Tutorial
The Proxy Component in React
A simple pattern to shield your codebase from change: centralise an implementation instead of scattering it across every file.
Aug 01, 20265 min
Read more