In tailwindcss v4, instead of using @apply
in @layer components
, the recommendation is to use tailwind variables like:
@layer components {
.card {
background-color: var(--color-white);
border-radius: var(--rounded-lg);
padding: var(--spacing-6);
box-shadow: var(--shadow-xl);
}
}
This style should be used in favor of using @apply
like the following, since apparently @apply
causes a lot of bugs and Adam is hinting he wants to get rid of it:
table not-prose {
@apply table-fixed w-full text-left text-sm;
}
To convert Tailwind CSS @apply usage into the new CSS variable-based approach recommended in Tailwind CSS v4, replace classes using @apply with their corresponding CSS custom properties (variables) where available, using Tailwind’s design tokens (like --spacing-*, --color-*, --text-*, --font-*, --font-weight-*
, etc.) inside @layer components
.
Note,
- Not all utilities have direct variable equivalents (e.g., table-fixed, text-left), so those are written as raw CSS.
- Dark mode styles are handled using @media (prefers-color-scheme: dark) or :is(.dark &) depending on your setup.
- More info on design tokens aka "theme variables" - https://tailwindcss.com/docs/theme
Here are some of the most commonly used variable namespaces:
--color-*
: For colors (e.g., --color-accent-500)--spacing-*
: For spacing (e.g., --spacing-4, --spacing-6)--font-size-*
: For font sizes (e.g., --font-size-sm)--font-weight-*
: For font weights (e.g., --font-semibold)--line-height-*
: For line heights--shadow-*
: For box shadows--rounded-*
: For border radii--z-*
: For z-index values--opacity-*
: For opacity levels--breakpoint-*
: For responsive breakpoints
Define or override in your theme:
@theme {
--color-brand: #1e40af;
--spacing-7: 1.75rem;
}
Then use in components:
.card {
background-color: var(--color-brand);
padding: var(--spacing-7);
}
THEME IS DEPRECATED
If tailwind finds a definition in your theme block, it uses that, but if not, it falls back to default. However, why even use something like line-height: var(--leading-relaxed);
rather than theme('something')
?
theme('something.value')
fetches the computed value from Tailwind's internal theme (including defaults and your@theme
overrides) at compile time. It's then replaced by a static value in the final CSS.var(--variable-name)
fetches the value of a CSS Custom Property (CSS variable) at runtime.
Here's why var(--leading-relaxed)
would be preferred over something like theme('lineHeight.relaxed')
:
-
Consistency with Custom Variables: When you define your own custom design tokens in your
@theme
block (like--color-esoliaamber-50
or--font-weight-light
), you are creating CSS variables. Usingvar()
to access these is the only way. By extension, if Tailwind also exposes its own utility values (likeleading-relaxed
) as CSS variables, usingvar()
creates a consistent pattern for accessing all your design tokens, whether custom or Tailwind-provided. -
Tailwind 4's Emphasis on CSS Variables: Tailwind v4 is designed to heavily leverage native CSS variables. For properties like
line-height
,font-weight
, and many colors, Tailwind internally generates CSS variables (e.g.,--leading-relaxed
,--tw-font-weight-light
,--tw-red-500
). When you usevar(--leading-relaxed)
, you are directly tapping into that dynamically generated CSS variable. -
Potential for Runtime Dynamism (though less common for line-height): While
line-height
isn't typically changed dynamically at runtime, using a CSS variable allows for it. For example, if you had a component where you wanted to adjust its line height based on a JavaScript state or a parent's CSS variable, usingvar()
would enable that flexibility, whereastheme()
would resolve to a static value that can't be easily overridden by later CSS or JS. -
Intellisense and Discoverability: With VSCode Intellisense, it can more easily suggest and complete CSS variables that exist in the global scope (either custom ones you define or those generated by Tailwind) than deeply nested
theme()
paths.
When to still use theme()
:
You should continue to use theme()
for values that are primarily configuration lookups or for properties where Tailwind doesn't typically expose a direct CSS variable:
- Breakpoints:
theme('screens.lg')
is the correct way to get the pixel value of a breakpoint for a media query. Breakpoints are conceptual "screen sizes" rather than direct CSS properties that vary. - Font Sizes:
font-size: theme('fontSize.lg');
is common becausefontSize.lg
isn't just a number; it often includes aline-height
property as well in Tailwind's configuration, andtheme()
correctly extracts thefont-size
part. - Arbitrary Values:
background-image: url(theme('backgroundImage.hero'));
(if you had ahero
image defined inbackgroundImage
in your config).
In essence:
- If Tailwind exposes a utility's value as a CSS variable (like
leading-relaxed
maps to--leading-relaxed
), it's generally best practice to usevar(--leading-relaxed)
. - If you're accessing a fundamental configuration value (like a breakpoint) or a property that might include multiple values (like a complex
font-size
definition that includesline-height
),theme()
is still the appropriate choice.
This approach lets you leverage Tailwind 4's capabilities fully and makes your CSS variables consistent with its internal workings.
Assuming a root font size of 16px:
--spacing() |
rem | px |
---|---|---|
0.25 | 0.0625 | 1px |
0.5 | 0.125 | 2px |
0.75 | 0.1875 | 3px |
1 | 0.25 | 4px |
1.25 | 0.3125 | 5px |
1.5 | 0.375 | 6px |
1.75 | 0.4375 | 7px |
2 | 0.5 | 8px |
2.25 | 0.5625 | 9px |
2.5 | 0.625 | 10px |
2.75 | 0.6875 | 11px |
3 | 0.75 | 12px |
3.25 | 0.8125 | 13px |
3.5 | 0.875 | 14px |
3.75 | 0.9375 | 15px |
4 | 1 | 16px |
4.25 | 1.0625 | 17px |
4.5 | 1.125 | 18px |
4.75 | 1.1875 | 19px |
5 | 1.25 | 20px |
5.25 | 1.3125 | 21px |
5.5 | 1.375 | 22px |
5.75 | 1.4375 | 23px |
6 | 1.5 | 24px |
6.25 | 1.5625 | 25px |
6.5 | 1.625 | 26px |
6.75 | 1.6875 | 27px |
7 | 1.75 | 28px |
7.25 | 1.8125 | 29px |
7.5 | 1.875 | 30px |
7.75 | 1.9375 | 31px |
8 | 2 | 32px |
8.25 | 2.0625 | 33px |
8.5 | 2.125 | 34px |
8.75 | 2.1875 | 35px |
9 | 2.25 | 36px |
9.25 | 2.3125 | 37px |
9.5 | 2.375 | 38px |
9.75 | 2.4375 | 39px |
10 | 2.5 | 40px |