Tailwind CSS 3 🎨 CSS-First Configuration Syntax (@import “tailwindcss”; and @theme)
In the previous chapter, you installed Tailwind CSS using either the Vite plugin or the CLI. In both cases, the setup ended with a single line in your CSS file: @import "tailwindcss";. That line does the work. It replaces the three @tailwind directives from v3 and brings in the entire default theme, the preflight base styles, and the utilities. But the import is only the beginning. The @theme directive is where you define your project’s design tokens — the colors, fonts, spacing, and breakpoints that make the utility classes unique to your application.
The shift from JavaScript configuration to CSS-first configuration is the headline change in Tailwind v4. The tailwind.config.js file is gone by default. Everything lives in the CSS file. The @theme directive defines the tokens. The compiler reads the tokens and generates the utilities. The same tokens become CSS custom properties, available at runtime for inline styles, JavaScript, and dynamic theming .
Key point: The @import "tailwindcss"; statement is the entry point. It imports the default theme, the preflight reset, and the utilities. The @theme directive extends or overrides the default theme. Every variable defined inside @theme generates both a utility class and a CSS custom property. The utility class is for use in markup. The custom property is for use in CSS, inline styles, and JavaScript. The two are the same value, exposed in two forms.
Why CSS-First Configuration Matters
The move from JavaScript to CSS is not cosmetic. It changes how the theme is consumed, how it is overridden, and what happens at runtime.
The runtime access problem. In v3, theme values lived in a JavaScript object. The compiler read the object and generated CSS. At runtime, the values were baked into the CSS. Changing a color required a rebuild. In v4, theme values are CSS custom properties. They are present in the :root of the generated CSS. JavaScript can read them with getComputedStyle and write them with style.setProperty. A theme can change without a rebuild .
The tooling problem. The JavaScript config required a separate file, a build step to read it, and a way to share the values with the CSS. The CSS-first config eliminates the separation. The theme is defined where it is used. The compiler reads the same file that the browser reads. The two are never out of sync .
The override problem. In v3, overriding a theme value meant editing the JavaScript config. The override was global. In v4, overriding a theme value can happen at any level: in the @theme block, in a :root rule, in a CSS layer, or at runtime. The cascade determines which value wins. The flexibility is greater, and the mental model is simpler .
The trade-off. The CSS-first model requires a mental shift. Developers who are used to the JavaScript config must learn the @theme syntax and the namespace conventions. The documentation is extensive, but the old patterns are everywhere online. The shift is real, but the result is a more cohesive system.
a. The @theme Directive
The @theme directive defines design tokens. Each token is a CSS variable with a specific namespace prefix. The prefix determines what utility classes are generated.
@import "tailwindcss";
@theme {
--color-brand: #16a34a;
--font-display: "Satoshi", sans-serif;
--breakpoint-3xl: 120rem;
--spacing-128: 32rem;
--radius-4xl: 2rem;
}
The --color-brand variable generates the utilities bg-brand, text-brand, and border-brand. The --font-display variable generates font-display. The --breakpoint-3xl variable generates the 3xl: variant. The --spacing-128 variable generates p-128, m-128, and so on. The --radius-4xl variable generates rounded-4xl .
The namespace is the part between -- and the first hyphen. The value is everything after the colon. The namespace determines the utility family. The value determines the utility’s effect.
| Namespace | Utility Generated | Example |
|---|---|---|
--color-* | bg-*, text-*, border-* | --color-brand → bg-brand |
--font-* | font-* | --font-display → font-display |
--breakpoint-* | *: variant | --breakpoint-3xl → 3xl: |
--spacing-* | p-*, m-*, gap-* | --spacing-128 → p-128 |
--radius-* | rounded-* | --radius-4xl → rounded-4xl |
--ease-* | ease-* | --ease-fluid → ease-fluid |
The theme variables are also emitted as CSS custom properties in the :root of the generated CSS. The compiler wraps them in @layer theme and adds them to :root and :host. A custom property can be used in any CSS rule, inline style, or JavaScript:
.custom-element {
background: var(--color-brand);
font-family: var(--font-display);
}
<div style="background-color: var(--color-brand)">
<p class="text-brand font-display">Styled with theme variables</p>
</div>
const brandColor = getComputedStyle(document.documentElement)
.getPropertyValue('--color-brand');
The three forms — utility class, custom property, and inline style — all reference the same value. The utility class is the most common. The custom property is for the cases where the utility class does not apply .
b. The Theme Modes: @theme, @theme inline, and @theme reference
The @theme directive has three modes that control how the variables are emitted and how they are resolved. The mode is declared after the directive name.
@theme (default). The variables are emitted as CSS custom properties in :root. The utilities reference the custom properties. The values can be overridden at runtime by changing the custom property. This is the mode for multi-theme systems and runtime customization .
@theme {
--color-primary: #3b82f6;
}
The generated CSS contains:
:root {
--color-primary: #3b82f6;
}
.bg-primary {
background-color: var(--color-primary);
}
@theme inline. The variables are not emitted as CSS custom properties. The values are inlined directly into the generated utility classes. This mode is used when the theme value references another CSS variable and you want the reference to resolve at the point of use. It is also used for single-theme designs where runtime overrides are not needed .
@theme inline {
--color-background: var(--background);
}
The generated CSS contains:
.bg-background {
background-color: var(--background);
}
The --color-background variable itself is not emitted. The utility class references var(--background) directly. This is the pattern used by shadcn/ui and other component libraries that define a base set of CSS variables and then map them to Tailwind utilities .
@theme reference. The variables are not emitted as CSS custom properties, but the utilities still reference the value. This mode is for values that are only needed in specific contexts, like breakpoints in media queries, and do not need to be available as CSS variables. It reduces the size of the generated CSS by avoiding :root bloat .
@theme reference {
--breakpoint-sm: 640px;
}
The generated CSS contains the sm: variant but does not emit --breakpoint-sm in :root. The value is used at compile time to generate the media query.
| Mode | Emits :root Variable | Utility References | Use Case |
|---|---|---|---|
@theme | Yes | var(--name) | Multi-theme, runtime overrides |
@theme inline | No | Inlined value | Single theme, component libraries |
@theme reference | No | Inlined value | Breakpoints, private calculations |
The three modes can be combined. @theme default inline marks the values as defaults that can be overridden, and inlines them. @theme reference inline is for values that are neither emitted nor referenced as variables .
c. Overriding and Extending the Default Theme
The @theme block extends the default theme by default. Adding a new variable adds a new utility. To override a default variable, you redefine it with the same name. To clear a category entirely, you set the wildcard to initial.
/* Extend: add a new color */
@theme {
--color-brand: #16a34a;
}
/* Override: replace the default blue-500 */
@theme {
--color-blue-500: #1e40af;
}
/* Clear: remove all default grays */
@theme {
--color-gray-*: initial;
--color-gray--50: #f8fafc;
--color-gray-100: #f1f5f9;
}
The --color-gray-*: initial; declaration removes every default gray. The subsequent declarations add the custom grays. The pattern is useful when you want full control over a color scale .
The default theme can be replaced entirely by importing only the preflight and utilities:
@import "tailwindcss/preflight";
@import "tailwindcss/utilities";
@theme {
--color-*: initial;
--color-primary: #3b82f6;
--color-secondary: #6b7280;
}
This approach removes every default color and defines a completely custom palette. It is the cleanest way to start a project with a specific brand system .
The theme() function can be used in custom CSS to reference theme values. In v4, the function uses CSS variable names:
.custom-component {
padding: theme(--spacing);
color: theme(--color-blue-500);
font-size: theme(--text-lg);
}
The function is automatically inlined in at-rules like media queries, where CSS variables cannot be used. For other contexts, the CSS variable itself is the recommended approach .
Complete Example Session
This session builds a custom theme with brand colors, fonts, and breakpoints, then uses the theme in markup and in custom CSS.
/* ============================================ */
/* PART 1: THE IMPORT AND THEME */
/* ============================================ */
/* src/index.css */
@import "tailwindcss";
@theme {
/* Brand colors */
--color-brand-50: #fef7ee;
--color-brand-100: #fdecd3;
--color-brand-200: #fad5a5;
--color-brand-300: #f7b76d;
--color-brand-400: #f38f33;
--color-brand-500: #f0710b;
--color-brand-600: #e15701;
--color-brand-700: #ba4104;
--color-brand-800: #94340a;
--color-brand-900: #782c0b;
--color-brand-950: #411401;
/* Fonts */
--font-display: "Satoshi", "sans-serif";
--font-body: "Inter", "system-ui", "sans-serif";
/* Breakpoints */
--breakpoint-3xl: 120rem;
/* Spacing */
--spacing-128: 32rem;
/* Border radius */
--radius-4xl: 2rem;
}
/* ============================================ */
/* PART 2: USING THE UTILITIES */
/* ============================================ */
/* The theme generates utilities: */
/* bg-brand-500, text-brand-600, border-brand-400 */
/* font-display, font-body */
/* 3xl: variant */
/* p-128, m-128, gap-128 */
/* rounded-4xl */
/* ============================================ */
/* PART 3: USING THE CUSTOM PROPERTIES */
/* ============================================ */
.brand-card {
background: var(--color-brand-50);
border: 1px solid var(--color-brand-200);
border-radius: var(--radius-4xl);
font-family: var(--font-body);
}
/* ============================================ */
/* PART 4: USING THE INLINE MODE */
/* ============================================ */
@theme inline {
--color-surface: var(--surface);
--color-text: var(--text);
}
:root {
--surface: #ffffff;
--text: #1a1a1a;
}
.dark {
--surface: #1a1a1a;
--text: #ffffff;
}
/* The utilities bg-surface and text-text will */
/* reference var(--surface) and var(--text) */
/* which change based on the .dark class. */
/* ============================================ */
/* PART 5: USING THE REFERENCE MODE */
/* ============================================ */
@theme reference {
--breakpoint-4xl: 160rem;
}
/* The 4xl: variant is generated. */
/* The --breakpoint-4xl variable is not emitted. */
/* ============================================ */
/* PART 6: EXTENDING THE DEFAULT THEME */
/* ============================================ */
@theme {
--color-brand: #16a34a;
--font-display: "Satoshi", sans-serif;
}
/* bg-brand, text-brand, font-display are generated. */
/* ============================================ */
/* PART 7: OVERRIDING A DEFAULT VALUE */
/* ============================================ */
@theme {
--color-blue-500: #1e40af;
}
/* The default blue-500 is replaced. */
/* bg-blue-500 now uses #1e40af. */
/* ============================================ */
/* PART 8: CLEARING A CATEGORY */
/* ============================================ */
@theme {
--color-gray-*: initial;
--color-gray-50: #f8fafc;
--color-gray-100: #f1f5f9;
--color-gray-900: #0f172a;
}
/* All default grays are removed. */
/* Only the custom grays are generated. */
/* ============================================ */
/* PART 9: USING theme() IN CUSTOM CSS */
/* ============================================ */
.custom-button {
padding: theme(--spacing-4) theme(--spacing-8);
background: theme(--color-brand-600);
color: white;
border-radius: theme(--radius-4xl);
font-family: theme(--font-display);
}
/* The theme() function resolves the values. */
/* It is inlined in at-rules like media queries. */
/* ============================================ */
/* PART 10: THE GENERATED CSS */
/* ============================================ */
/* The @theme block generates: */
/* 1. CSS custom properties in :root, :host */
/* 2. Utility classes that reference them */
/* 3. Variants for breakpoints and states */
/* The @theme inline block generates: */
/* 1. Utility classes that inline the value */
/* 2. No CSS custom properties */
/* The @theme reference block generates: */
/* 1. The utilities or variants */
/* 2. No CSS custom properties */
The ten parts cover the import and theme, using the utilities, using the custom properties, the inline mode, the reference mode, extending the default theme, overriding a default value, clearing a category, using theme() in custom CSS, and the generated CSS.
Quick Reference
The Theme Directives
| Directive | Purpose |
|---|---|
@import "tailwindcss" | Import the default theme and utilities |
@theme | Define design tokens |
@theme inline | Inline values, no :root variables |
@theme reference | Generate utilities without :root variables |
@theme default | Mark values as overridable defaults |
The Theme Namespaces
| Namespace | Utility Family |
|---|---|
--color-* | bg-*, text-*, border-* |
--font-* | font-* |
--breakpoint-* | *: variants |
--spacing-* | p-*, m-*, gap-* |
--radius-* | rounded-* |
--ease-* | ease-* |
The Override Patterns
| Pattern | Effect |
|---|---|
--color-blue-500: #1e40af | Override a default value |
--color-gray-*: initial | Clear all default grays |
--color-brand: #16a34a | Add a new color |
--font-display: "Satoshi" | Add a new font |
The theme() Function
| Usage | Purpose |
|---|---|
theme(--color-blue-500) | Resolve a theme value |
theme(--spacing, 1rem) | With fallback |
theme(--spacing inline) | Force inline resolution |
theme(--breakpoint-md) | In media queries (auto-inlined) |
Best Practices
✅ Do This:
/* Use semantic naming */
@theme {
--color-primary-500: #3b82f6; /* ✅ */
}
/* Group related tokens */
@theme {
--color-brand-50: #fef7ee; /* ✅ */
--color-brand-500: #f0710b;
--color-brand-900: #782c0b;
}
/* Use @theme inline for component library patterns */
@theme inline {
--color-surface: var(--surface); /* ✅ */
}
/* Use @theme reference for breakpoints */
@theme reference {
--breakpoint-sm: 640px; /* ✅ */
}
❌ Don’t Do This:
/* Don't use arbitrary values when theme tokens exist */
<div class="bg-[#f0710b]"> /* ⚠️ */
/* Don't define theme tokens outside @theme */
:root {
--color-brand: #f0710b; /* Tailwind does not know about this */ /* ❌ */
}
/* Don't use @theme nested inside a selector */
.card {
@theme { --color-brand: #f0710b; } /* ❌ */
}
/* Don't forget the namespace prefix */
@theme {
--brand: #f0710b; /* generates no utility */ /* ❌ */
}
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Utility not generated | Wrong namespace | Use --color-* for colors |
Variable not in :root | Used @theme inline | Use @theme for runtime access |
| Self-referential var | --font-sans: var(--font-sans) | Use a different name |
| Override not working | Defined outside @theme | Move into @theme |
| Breakpoint not working | Used @theme instead of @theme reference | Use reference mode |
Real-World Examples
1. Brand Colors
@theme {
--color-brand-500: #f0710b;
--color-brand-600: #e15701;
}
2. Custom Font
@theme {
--font-display: "Satoshi", sans-serif;
}
3. Custom Breakpoint
@theme {
--breakpoint-3xl: 120rem;
}
4. Override Default
@theme {
--color-blue-500: #1e40af;
}
5. Clear Category
@theme {
--color-gray-*: initial;
--color-gray-50: #f8fafc;
--color-gray-900: #0f172a;
}
6. Inline for Theme Switching
@theme inline {
--color-surface: var(--surface);
}
7. Reference Mode
@theme reference {
--breakpoint-sm: 640px;
}
8. theme() in Custom CSS
.custom {
color: theme(--color-brand-500);
}
9. Runtime Override
document.documentElement.style.setProperty('--color-primary', '#16a34a');
10. Semantic Naming
@theme {
--color-primary-500: #3b82f6;
--color-secondary-500: #6b7280;
}
Visual
The CSS-First Configuration
┌──────────────────────────────────────────────┐
│ @import "tailwindcss"; │
│ │
│ @theme { │
│ --color-brand: #16a34a; │
│ --font-display: "Satoshi"; │
│ --breakpoint-3xl: 120rem; │
│ } │
│ │
│ Generates: │
│ ├─ Utility classes (bg-brand, font-display)│
│ ├─ CSS custom properties (var(--color-brand))│
│ └─ Variants (3xl:) │
│ │
└──────────────────────────────────────────────┘
The Three Modes
┌──────────────────────────────────────────────┐
│ @theme │
│ → :root { --color-brand: #16a34a } │
│ → .bg-brand { background: var(--color-brand) }│
│ │
│ @theme inline │
│ → .bg-brand { background: #16a34a } │
│ │
│ @theme reference │
│ → .sm\:block { @media (width >= 640px) } │
│ │
└──────────────────────────────────────────────┘
The Override Model
┌──────────────────────────────────────────────┐
│ Extend: │
│ @theme { --color-brand: #16a34a } │
│ → adds bg-brand │
│ │
│ Override: │
│ @theme { --color-blue-500: #1e40af } │
│ → replaces default blue-500 │
│ │
│ Clear: │
│ @theme { --color-gray-*: initial } │
│ → removes all default grays │
│ │
└──────────────────────────────────────────────┘
The Runtime Flow
┌──────────────────────────────────────────────┐
│ CSS: │
│ @theme { --color-primary: #3b82f6 } │
│ @theme inline { --color-surface: var(--surface) }│
│ │
│ Runtime: │
│ document.documentElement.style │
│ .setProperty('--surface', '#1a1a1a') │
│ │
│ The .dark class can override the surface. │
│ │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Import | @import "tailwindcss"; |
| Theme directive | @theme |
| Inline mode | @theme inline |
| Reference mode | @theme reference |
| Color namespace | --color-* |
| Font namespace | --font-* |
| Breakpoint namespace | --breakpoint-* |
| Spacing namespace | --spacing-* |
| Override pattern | Redefine the variable |
| Clear pattern | --namespace-*: initial |
| Runtime access | var(--name) |
Key takeaways:
@import "tailwindcss";is the entry point. It replaces the three@tailwinddirectives from v3. It imports the default theme, the preflight reset, and the utilities. Thetailwind.config.jsfile is no longer the primary configuration method .- The
@themedirective defines design tokens. Each token is a CSS variable with a namespace prefix. The namespace determines what utility classes are generated. The--color-brandvariable generatesbg-brand,text-brand, andborder-brand. - The theme variables are emitted as CSS custom properties. They are available in
:rootand:host. They can be used in custom CSS, inline styles, and JavaScript. The same value is exposed as both a utility class and a custom property . - The
@theme inlinemode inlines values into utilities. It does not emit:rootvariables. It is used for component library patterns where the utilities reference a base set of CSS variables that change at runtime . - The
@theme referencemode generates utilities without emitting variables. It is used for breakpoints and private calculations where the value does not need to be available as a CSS variable. It reduces the size of the generated CSS . - The theme can be extended, overridden, or cleared. Adding a variable extends the default theme. Redefining a variable overrides it. Setting a namespace to
initialclears the category. The patterns are explicit and predictable . - The
theme()function resolves theme values in custom CSS. It uses CSS variable names and is automatically inlined in at-rules like media queries. For other contexts, the CSS variable itself is the recommended approach .
Remember: The @import "tailwindcss"; statement is the entry point. The @theme directive defines the design tokens. The namespaces determine the utilities. The custom properties expose the values at runtime. The three modes — default, inline, and reference — control how the values are emitted. The theme can be extended, overridden, or cleared. The CSS-first configuration is the headline change in v4. The tailwind.config.js file is gone. The theme lives in the CSS.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!