In every Next.js project, layout.tsx has a body tag that looks like this:
<body className={`${inter.variable} antialiased`}>Two things are happening here that are easy to gloss over. They are doing very different jobs.
inter.variable
When you load a font in Next.js, you give it a variable name:
const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
})inter.variable is not the font itself. It is a generated CSS class name — something like __variable_a1b2c3. When that class is applied to an element, it injects a CSS custom property into scope:
.__variable_a1b2c3 {
--font-inter: 'Inter', sans-serif;
}Putting this class on <body> makes --font-inter available to every element on the page. Tailwind picks it up through the @theme block in globals.css:
@theme {
--font-inter: var(--font-inter), sans-serif;
}This registers --font-inter as a Tailwind design token, which generates the font-inter utility class. When you apply font-inter to an element, it sets font-family: var(--font-inter) — which resolves to the Inter font loaded by Next.js with full subsetting, optimisation, and self-hosting.
antialiased
This is a Tailwind utility that applies two CSS properties:
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;Without it, browsers on macOS render fonts with subpixel antialiasing — a technique that uses the RGB channels of adjacent pixels to sharpen text. The result looks bolder and heavier. With antialiased, the browser switches to grayscale antialiasing, which renders text thinner, lighter, and crisper.
Applying it to <body> makes it the default for the entire page. It is purely visual — it does not affect layout or accessibility.
@theme vs @theme inline in Tailwind v4
This distinction came up while building a runtime theme switcher. The goal: define colour tokens that update when a data attribute changes on <html>.
[data-theme="dark"] {
--color-background: #0a0f1e;
--color-text: #e8eef8;
}The plan was that Tailwind utilities like bg-background and text-text would automatically pick up the new values. They did not — and the reason is the difference between @theme and @theme inline.
In my project
The attribute is data-season and the values are the seasonal palette. SeasonToggle sets data-season="winter" on <html> at runtime and expects every colour utility to update instantly.
@theme inline — values baked at build time
The inline modifier tells Tailwind to resolve token values at build time and write them directly into utility classes:
/* globals.css */
@theme inline {
--color-background: #E4E5D9;
}
/* generated output */
.bg-background {
background-color: #E4E5D9; /* static hex, locked at build time */
}The hex value is frozen. Updating --color-background at runtime via a CSS attribute selector has no effect on bg-background — the utility already has its answer baked in.
@theme — values stay as CSS variables
Without inline, Tailwind writes the CSS variable into :root and makes utilities reference it:
/* globals.css */
@theme {
--color-background: #E4E5D9;
}
/* generated output */
:root {
--color-background: #E4E5D9;
}
.bg-background {
background-color: var(--color-background); /* dynamic reference */
}Now bg-background resolves at runtime. When [data-theme="dark"] overrides --color-background, every element using the utility immediately gets the new value — no JavaScript required, no class toggling, just CSS cascade.
When to use which
Use @theme inline for values that never change at runtime — spacing scale, border radius, font sizes. These benefit from being inlined because the browser doesn't need to resolve a variable reference on every paint.
Use @theme for values you intend to override at runtime — colours, any token driven by a theme, user preference, or dynamic state. The var() reference is the mechanism that makes runtime theming possible.
Putting it together
None of this works unless the theme tokens are defined with @theme, not @theme inline. That single word determines whether your design tokens are frozen at build time or alive at runtime.
In my project
The season switcher works entirely through CSS variables. Selecting a season sets data-season="winter" on <html>. The cascade does the rest — every Tailwind colour utility updates instantly, sitewide, with no JavaScript touching individual elements.