Tailwind CSS Handbook v4.3

Theme Variable Reference

Every @theme namespace in Tailwind v4 — the utilities each generates, the full default scales, and how to extend, override, disable and read them.

Theme variables are CSS custom properties declared inside @theme. A variable's namespace — the part before the last segment — decides which utilities it generates. Everything on this page is a real default in Tailwind v4.

Namespace summary#

NamespaceGeneratesExample
--color-*Every colour utilitybg-mint-500, text-mint-500, ring-mint-500
--font-*Font familyfont-display
--text-*Font size, plus paired line height, letter spacing and weighttext-hero
--font-weight-*Font weightfont-book
--tracking-*Letter spacingtracking-caps
--leading-*Line heightleading-prose
--tab-size-*Tab size v4.3tab-github
--breakpoint-*Responsive variants3xl:flex
--container-*Container query variants and max-width@3xl:grid, max-w-3xl
--spacing-*Named spacing valuesp-gutter
--radius-*Border radiusrounded-card
--shadow-*Box shadowshadow-card
--inset-shadow-*Inset box shadowinset-shadow-well
--drop-shadow-*Drop shadow filterdrop-shadow-glow
--text-shadow-*Text shadow v4.1text-shadow-emboss
--blur-*Blur and backdrop blurblur-frost, backdrop-blur-frost
--perspective-*3D perspectiveperspective-stage
--zoom-*Zoom v4.3zoom-compact
--aspect-*Aspect ratioaspect-poster
--ease-*Transition timing functionease-snappy
--animate-*Animation shorthandanimate-wiggle

Two variables are not namespaces but scalars in their own right:

VariableEffect
--spacingThe multiplier behind every numeric spacing and sizing utility
--default-*Base values the preflight and some utilities read — see Default variables

--color-*#

The default palette has 22 families, each with eleven steps — 50 100 200 300 400 500 600 700 800 900 950:

GroupFamilies
Warmred orange amber yellow
Greenlime green emerald teal
Bluecyan sky blue indigo
Purple/pinkviolet purple fuchsia pink rose
Neutralslate gray zinc neutral stone
Muted v4.2mauve olive mist taupe

Plus --color-black: #000 and --color-white: #fff. The keywords transparent, current and inherit are built in and are not theme variables.

Every family is defined in oklch(), which is why the shade ramps stay perceptually even and why bg-sky-500/40 mixes predictably.

Add a scale
@theme {
  --color-brand-50:  oklch(0.97 0.02 264);
  --color-brand-500: oklch(0.62 0.19 264);
  --color-brand-900: oklch(0.35 0.14 264);
}

One --color-* entry generates bg-*, text-*, border-*, outline-*, ring-*, inset-ring-*, shadow-*, inset-shadow-*, drop-shadow-*, text-shadow-*, divide-*, decoration-*, accent-*, caret-*, fill-*, stroke-*, from-*, via-*, to-*, placeholder-*, selection-*, marker-* and the scrollbar-thumb-* / scrollbar-track-* pair.

--font-*#

VariableDefault
--font-sans-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', 'Noto Sans', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'
--font-serifui-serif, Georgia, Cambria, 'Times New Roman', Times, serif
--font-monoui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace

--font-* accepts two sub-properties:

CSS
@theme {
  --font-display: "Satoshi", ui-sans-serif, sans-serif;
  --font-display--font-feature-settings: "cv02", "cv03";
  --font-display--font-variation-settings: "opsz" 32;
}

font-display then sets all three declarations at once.

--text-*#

Each size ships with a paired line height.

VariableFont size--line-height
--text-xs0.75remcalc(1 / 0.75)
--text-sm0.875remcalc(1.25 / 0.875)
--text-base1remcalc(1.5 / 1)
--text-lg1.125remcalc(1.75 / 1.125)
--text-xl1.25remcalc(1.75 / 1.25)
--text-2xl1.5remcalc(2 / 1.5)
--text-3xl1.875remcalc(2.25 / 1.875)
--text-4xl2.25remcalc(2.5 / 2.25)
--text-5xl3rem1
--text-6xl3.75rem1
--text-7xl4.5rem1
--text-8xl6rem1
--text-9xl8rem1

Sub-property syntax#

A second -- suffix attaches a companion declaration to a font-size token:

CSS
@theme {
  --text-hero: 4rem;
  --text-hero--line-height: 1.05;
  --text-hero--letter-spacing: -0.02em;
  --text-hero--font-weight: 700;
}
HTML
<h1 class="text-hero">Sets size, leading, tracking and weight.</h1>

Override any of them inline — text-hero leading-tight wins, because the explicit utility comes later in the cascade. The text-<size>/<number> shorthand does the same for line height: text-hero/none, text-sm/6.

--font-weight-*#

VariableDefaultUtility
--font-weight-thin100font-thin
--font-weight-extralight200font-extralight
--font-weight-light300font-light
--font-weight-normal400font-normal
--font-weight-medium500font-medium
--font-weight-semibold600font-semibold
--font-weight-bold700font-bold
--font-weight-extrabold800font-extrabold
--font-weight-black900font-black

--tracking-*#

VariableDefault
--tracking-tighter-0.05em
--tracking-tight-0.025em
--tracking-normal0em
--tracking-wide0.025em
--tracking-wider0.05em
--tracking-widest0.1em

--leading-*#

VariableDefault
--leading-tight1.25
--leading-snug1.375
--leading-normal1.5
--leading-relaxed1.625
--leading-loose2

leading-none is a static utility for line-height: 1 and has no theme variable. Numeric leading-<number> utilities are derived from --spacing: leading-7 is calc(var(--spacing) * 7).

--spacing#

CSS
@theme {
  --spacing: 0.25rem;
}

One variable drives p-*, m-*, gap-*, space-*, w-*, h-*, size-*, inset-*, translate-*, leading-<number>, indent-* and more. p-4 compiles to calc(var(--spacing) * 4), so changing --spacing rescales the whole system proportionally.

Values are generated on demand — p-13, mt-96 and gap-7 all work without configuration.

Named spacing tokens live in the --spacing-* namespace:

CSS
@theme {
  --spacing-gutter: 1.5rem;
}
HTML
<div class="px-gutter gap-gutter">…</div>

--breakpoint-*#

VariableDefaultVariant
--breakpoint-sm40remsm: and max-sm:
--breakpoint-md48remmd: and max-md:
--breakpoint-lg64remlg: and max-lg:
--breakpoint-xl80remxl: and max-xl:
--breakpoint-2xl96rem2xl: and max-2xl:

Adding --breakpoint-3xl: 120rem creates 3xl: and max-3xl: at once. The container utility also reads this namespace.

--container-*#

VariableDefaultVariableDefault
--container-3xs16rem--container-2xl42rem
--container-2xs18rem--container-3xl48rem
--container-xs20rem--container-4xl56rem
--container-sm24rem--container-5xl64rem
--container-md28rem--container-6xl72rem
--container-lg32rem--container-7xl80rem
--container-xl36rem

This namespace does double duty: it sizes max-w-* and it defines the container query variants @3xs: through @7xl: and their @max-* counterparts.

--radius-*#

VariableDefaultPixels
--radius-xs0.125rem2px
--radius-sm0.25rem4px
--radius-md0.375rem6px
--radius-lg0.5rem8px
--radius-xl0.75rem12px
--radius-2xl1rem16px
--radius-3xl1.5rem24px
--radius-4xl2rem32px

rounded-none (0) and rounded-full (calc(infinity * 1px)) are static utilities with no variable.

--shadow-* and friends#

VariableDefault
--shadow-2xs0 1px rgb(0 0 0 / 0.05)
--shadow-xs0 1px 2px 0 rgb(0 0 0 / 0.05)
--shadow-sm0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)
--shadow-md0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)
--shadow-lg0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)
--shadow-xl0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)
--shadow-2xl0 25px 50px -12px rgb(0 0 0 / 0.25)
VariableDefault
--inset-shadow-2xsinset 0 1px rgb(0 0 0 / 0.05)
--inset-shadow-xsinset 0 1px 1px rgb(0 0 0 / 0.05)
--inset-shadow-sminset 0 2px 4px rgb(0 0 0 / 0.05)
VariableDefault
--drop-shadow-xs0 1px 1px rgb(0 0 0 / 0.05)
--drop-shadow-sm0 1px 2px rgb(0 0 0 / 0.15)
--drop-shadow-md0 3px 3px rgb(0 0 0 / 0.12)
--drop-shadow-lg0 4px 4px rgb(0 0 0 / 0.15)
--drop-shadow-xl0 9px 7px rgb(0 0 0 / 0.1)
--drop-shadow-2xl0 25px 25px rgb(0 0 0 / 0.15)
Variable v4.1Default
--text-shadow-2xs0px 1px 0px rgb(0 0 0 / 0.15)
--text-shadow-xs0px 1px 1px rgb(0 0 0 / 0.2)
--text-shadow-sm0px 1px 0px rgb(0 0 0 / 0.075), 0px 1px 1px rgb(0 0 0 / 0.075), 0px 2px 2px rgb(0 0 0 / 0.075)
--text-shadow-md0px 1px 1px rgb(0 0 0 / 0.1), 0px 1px 2px rgb(0 0 0 / 0.1), 0px 2px 4px rgb(0 0 0 / 0.1)
--text-shadow-lg0px 1px 2px rgb(0 0 0 / 0.1), 0px 3px 2px rgb(0 0 0 / 0.1), 0px 4px 8px rgb(0 0 0 / 0.1)

shadow-none, inset-shadow-none and text-shadow-none are static resets.

--blur-*#

VariableDefault
--blur-xs4px
--blur-sm8px
--blur-md12px
--blur-lg16px
--blur-xl24px
--blur-2xl40px
--blur-3xl64px

One entry generates two utilities: blur-* and backdrop-blur-*.

--perspective-*#

VariableDefault
--perspective-dramatic100px
--perspective-near300px
--perspective-normal500px
--perspective-midrange800px
--perspective-distant1200px

Smaller values sit the camera closer and exaggerate the effect. perspective-none is static.

--aspect-*#

VariableDefault
--aspect-video16 / 9

aspect-square (1 / 1) and aspect-auto are static, and any ratio works inline: aspect-3/2, aspect-21/9.

--ease-*#

VariableDefault
--ease-incubic-bezier(0.4, 0, 1, 1)
--ease-outcubic-bezier(0, 0, 0.2, 1)
--ease-in-outcubic-bezier(0.4, 0, 0.2, 1)

ease-linear and ease-initial are static utilities.

--animate-*#

VariableDefault
--animate-spinspin 1s linear infinite
--animate-pingping 1s cubic-bezier(0, 0, 0.2, 1) infinite
--animate-pulsepulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite
--animate-bouncebounce 1s infinite

Define keyframes inside @theme so they travel with the token:

app.css
@theme {
  --animate-wiggle: wiggle 1s ease-in-out infinite;

  @keyframes wiggle {
    0%, 100% { transform: rotate(-3deg); }
    50%      { transform: rotate(3deg); }
  }
}

--tab-size-* and --zoom-*#

v4.3 Both namespaces ship empty. tab-<number> and zoom-<number> work out of the box from bare values; the namespaces exist so you can name the ones you use repeatedly.

CSS
@theme {
  --tab-size-github: 8;
  --zoom-compact: 0.85;
}
HTML
<pre class="tab-github">…</pre>
<div class="zoom-compact">…</div>

Default variables#

These are not namespaces. They set base values the preflight and a few utilities read.

VariableDefault
--default-transition-duration150ms
--default-transition-timing-functioncubic-bezier(0.4, 0, 0.2, 1)
--default-font-family--theme(--font-sans, initial)
--default-font-feature-settings--theme(--font-sans--font-feature-settings, initial)
--default-font-variation-settings--theme(--font-sans--font-variation-settings, initial)
--default-mono-font-family--theme(--font-mono, initial)
--default-mono-font-feature-settings--theme(--font-mono--font-feature-settings, initial)
--default-mono-font-variation-settings--theme(--font-mono--font-variation-settings, initial)

Change --default-transition-duration to retune every bare transition in the project at once.

Extending and overriding#

Add a token#

app.css
@import "tailwindcss";

@theme {
  --font-poppins: Poppins, sans-serif;
}

New utility, defaults untouched.

Override one default#

CSS
@theme {
  --breakpoint-sm: 30rem;
}

Remove one default#

CSS
@theme {
  --breakpoint-2xl: initial;
}

2xl: and max-2xl: stop existing.

Replace an entire namespace#

CSS
@theme {
  --color-*: initial;
  --color-white: #fff;
  --color-purple: #3f3cbb;
  --color-midnight: #121063;
  --color-tahiti: #3ab7bf;
  --color-bermuda: #78dcca;
}

bg-red-500 no longer compiles. This is the cleanest way to enforce a closed palette — a typo becomes a build-visible missing class rather than a plausible-looking wrong colour.

Start from nothing#

CSS
@theme {
  --*: initial;
  --spacing: 4px;
  --font-body: Inter, sans-serif;
  --color-lagoon: oklch(0.72 0.11 221.19);
  --color-coral: oklch(0.74 0.17 40.24);
}

@theme inline#

By default a utility references the variable: .font-sans { font-family: var(--font-sans) }. If --font-sans itself holds var(--font-inter), that indirection is captured where @theme was declared, so a value set later on a wrapper element never reaches it.

inline resolves the reference at build time and emits the inner variable directly:

app.css
@theme inline {
  --font-sans: var(--font-inter);
}
Generated
.font-sans {
  font-family: var(--font-inter);
}

Use it for any token whose value is a runtime variable — Next.js font loading and semantic colour aliases that flip between light and dark are the two common cases.

@theme static#

Tailwind only emits the CSS variables that some utility actually uses. static emits every variable in the block regardless:

CSS
@theme static {
  --color-chart-1: oklch(0.7 0.15 200);
  --color-chart-2: oklch(0.7 0.15 30);
}

Reach for it when JavaScript reads a token no class references — chart palettes, canvas rendering, animation libraries.

Referencing tokens outside utilities#

In custom CSS#

CSS
@layer components {
  .prose-callout {
    border-left: 3px solid var(--color-brand-500);
    padding-inline: --spacing(4);
    font-size: var(--text-sm);
    color: --alpha(var(--color-gray-950) / 10%);
  }
}

--spacing(4) expands to calc(var(--spacing) * 4); --alpha() compiles to a color-mix().

In arbitrary values#

HTML
<div class="rounded-[calc(var(--radius-xl)-1px)]">…</div>

With the shorthand syntax#

HTML
<div class="bg-(--color-brand-500) p-(--gutter)">…</div>

bg-(--x) is exactly bg-[var(--x)].

In inline styles#

HTML
<div style="background-color: var(--color-brand-500)">…</div>

In JavaScript#

JS
const styles = getComputedStyle(document.documentElement)
const shadow = styles.getPropertyValue('--shadow-xl')
JSX
<motion.div animate={{ backgroundColor: 'var(--color-brand-500)' }} />

Passing the var() string is preferable to reading the computed value — it keeps the animation live across a theme change.

Common mistakes#

  • Declaring tokens in :root instead of @theme. A plain custom property produces a variable but no utility.
  • Inventing a namespace. --brand-color, --size-* and --space-* generate nothing. Only the namespaces in the table above are recognised.
  • Putting @theme before @import "tailwindcss". The import has to come first.
  • Forgetting inline for runtime variables. The classic symptom is a webfont that never applies.
  • Expecting getPropertyValue to find an unused token. Wrap it in @theme static, or reference it from at least one class.
  • Using theme() in new code. theme(spacing.12) still works but is deprecated — use var(--spacing) and --spacing().