Customising the theme with @theme

Tailwind CSS · Practical · 12 min

@theme is how every v4 project brings in its own brand, and it replaces most of tailwind.config.js.

In v4 the design system lives in CSS. Theme variables are custom properties declared in an @theme block, and their namespace decides which utilities they create:

  • --color-brand-500 creates bg-brand-500, text-brand-500, border-brand-500 and so on,
  • --font-display creates font-display,
  • --text-*, --font-weight-*, --leading-*, --tracking-* for type,
  • --breakpoint-* for responsive variants, --container-* for container sizes and max-w-*,
  • --radius-*, --shadow-*, --ease-*, --animate-*, and --spacing for the whole spacing scale.

Every theme variable is also emitted as a normal CSS variable on :root (when used), so you can read it anywhere: var(--color-brand-500) in custom CSS, inline styles or JavaScript.

To extend, just add variables. To override a default, redeclare it with the same name. To remove a whole namespace, set it to initial (--color-*: initial;) and define only your own colours; --*: initial clears the entire default theme.

Use plain :root variables for values that shouldn't create utilities. Use @theme inline when a theme variable refers to another variable, so utilities use the final value rather than a reference that might resolve differently deeper in the tree.

To share a theme across projects, put the @theme block in its own CSS file and @import it.

Example

/* app.css */
@import "tailwindcss";

@theme {
  --font-display: "Satoshi", ui-sans-serif, system-ui, sans-serif;

  --color-brand-50: oklch(0.97 0.02 265);
  --color-brand-500: oklch(0.60 0.20 265);
  --color-brand-900: oklch(0.30 0.12 265);

  --breakpoint-3xl: 120rem;          /* enables 3xl: */
  --radius-card: 1.25rem;            /* enables rounded-card */
  --ease-snappy: cubic-bezier(0.2, 0, 0, 1);  /* enables ease-snappy */
}

/* Usage: <h1 class="font-display text-brand-900">...</h1>
          <div class="rounded-card bg-brand-50 3xl:p-12">...</div>
   Plain CSS can read the same tokens: */
.chart-line { stroke: var(--color-brand-500); }

Remember

  • @theme variables generate utilities based on their namespace
  • They're also real CSS variables usable anywhere
  • Redeclare to override; --color-*: initial removes a namespace
  • Use :root for variables that shouldn't create utilities

Watch out for

  • Putting brand colours in :root and wondering why bg-brand-500 doesn't exist.

Check yourself

  1. Which declaration in @theme makes a class like shadow-glow available?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: tailwindcss.com/docs/theme