In v4 the palette lives in CSS: one --color-* variable per color
Tailwind v4 @theme colors from a palette
In Tailwind CSS v4, colors are defined in CSS: every --color-* variable inside @theme becomes a color for the utilities, so --color-primary: #00ac84; gives bg-primary, text-primary and border-primary. Recolor (recolor.page) exports a palette as such a block. Tailwind's own default colors are written in OKLCH, and --color-*: initial removes them all.
Last updated
| You write | Inside | You get |
|---|---|---|
--color-primary: #00ac84; | @theme | bg-primary, text-primary, fill-primary and the rest |
--color-1: #00ac84; | @theme | bg-1, text-1 |
--color-mint-500: oklch(0.72 0.11 178); | @theme | bg-mint-500 (the example in Tailwind's docs) |
--color-*: initial; | @theme | No default colors, only yours |
--color-surface: var(--surface); | @theme inline | A utility that follows a variable you redefine, for dark mode |
A palette as an @theme block
Tailwind v4 reads its design tokens from CSS. Any variable in the --color-* namespace inside @theme becomes a
color for every color utility, and stays available as a normal CSS variable too. This is the block Recolor exports
for the Evergreen palette:
@theme {
--color-1: #00ac84;
--color-2: #1d6a59;
--color-3: #031f17;
--color-4: #88d8c0;
--color-5: #c6ede1;
--color-primary: #00ac84;
--color-secondary: #1d6a59;
--color-accent: #031f17;
}Put it after @import "tailwindcss"; in your main stylesheet. The numbered names follow the palette's order; the
three role names give bg-primary, text-secondary and ring-accent. They hold the hex value itself, so the
block needs no @theme inline. Export formats shows the other formats of the same palette.
Naming: roles or scales
Tailwind's default colors are scales, red-50 to red-950, and many design systems copy that. A palette from a
generator is usually a handful of roles instead: a primary, a secondary, an accent, a dark and a light. Both work
in @theme; pick one per project. Roles keep templates honest when the palette changes, since bg-primary stays
the brand however the brand looks. If you need shades of a role, color-mix() can derive them in CSS, as
the CSS variables guide shows.
OKLCH in the theme
Tailwind 4.3.3 writes 286 of its default colors in OKLCH, for example:
--color-red-500: oklch(63.7% 0.237 25.331);
--color-blue-500: oklch(62.3% 0.214 259.815);Black and white are the exceptions, written as hex. Your own colors can be hex, rgb() or oklch(); Tailwind does
not convert them. OKLCH is worth it when you adjust a palette by hand, because its lightness number means what it
says; What OKLCH is explains why.
Removing the default colors
Defining your own colors adds them next to Tailwind's. To leave only yours, reset the namespace first:
@theme {
--color-*: initial;
--color-primary: #00ac84;
}Every default color utility, bg-red-500 and text-white included, then disappears, so add white and black back if
you use them.
Dark mode with @theme inline
Theme variables have to sit at the top level, not inside a selector or a media query. For colors that change with a
theme, point the theme variable at an ordinary variable with @theme inline, and redefine that one per theme:
@theme inline {
--color-surface: var(--surface);
--color-text: var(--text);
}
:root {
--surface: #ffffff;
--text: #031f17;
}
.dark {
--surface: #031f17;
--text: #c6ede1;
}bg-surface then follows whichever value applies. Recolor itself shows a site in the theme it has, so to see a
palette on a dark version of your site, load that version of the page; Loading a page
covers the options.
Common mistakes when moving colors to v4
- Defining colors outside
@theme. A variable on:rootis just a variable; only--color-*inside@themecreates utilities. - Nesting
@themein a selector or a media query. Theme variables must be top-level; use@theme inlinewith a plain variable for anything that changes by theme. - Keeping the v3 config. A
colorsobject intailwind.config.jsis the old way; in a v4 project with a CSS entry point, the@themeblock is where colors go. - Forgetting the defaults are still there. Your colors are added next to Tailwind's until you reset the
namespace with
--color-*: initial, which also removes white and black. - Naming a color after its hue.
--color-tealbreaks the day the brand is no longer teal; a role such as--color-primarysurvives the next palette, which is why Recolor's export adds role names.
Common questions
Do I still need tailwind.config.js for colors in Tailwind v4?
No. In v4 colors are --color-* variables in an @theme block in your CSS.
Can I use hex colors in @theme instead of OKLCH?
Yes. Any CSS color works; Tailwind's own defaults happen to be OKLCH, apart from black and white.
Does --color-*: initial also remove white and black?
Yes. It removes every color in the namespace, --color-white and --color-black included.
Can I use opacity modifiers such as bg-primary/50 with my own colors?
Yes. The /50 modifier sets the alpha of any theme color, your own included.
Can theme variables be defined inside .dark or a media query?
No. Theme variables must be top-level; use @theme inline pointing at a variable that you redefine under .dark.
Can Recolor export a palette as a Tailwind v4 block?
Yes. Its Export panel has a Tailwind tab that writes the @theme block shown above for whatever palette is on screen.
Sources
- Theme variables, Tailwind CSS docs tailwindcss.com
- Colors, Tailwind CSS docs tailwindcss.com
- oklch(), MDN developer.mozilla.org