| |

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.

NamespaceUtility GeneratedExample
--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.

ModeEmits :root VariableUtility ReferencesUse Case
@themeYesvar(--name)Multi-theme, runtime overrides
@theme inlineNoInlined valueSingle theme, component libraries
@theme referenceNoInlined valueBreakpoints, 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

DirectivePurpose
@import "tailwindcss"Import the default theme and utilities
@themeDefine design tokens
@theme inlineInline values, no :root variables
@theme referenceGenerate utilities without :root variables
@theme defaultMark values as overridable defaults

The Theme Namespaces

NamespaceUtility Family
--color-*bg-*, text-*, border-*
--font-*font-*
--breakpoint-**: variants
--spacing-*p-*, m-*, gap-*
--radius-*rounded-*
--ease-*ease-*

The Override Patterns

PatternEffect
--color-blue-500: #1e40afOverride a default value
--color-gray-*: initialClear all default grays
--color-brand: #16a34aAdd a new color
--font-display: "Satoshi"Add a new font

The theme() Function

UsagePurpose
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

PitfallWhy It HappensFix
Utility not generatedWrong namespaceUse --color-* for colors
Variable not in :rootUsed @theme inlineUse @theme for runtime access
Self-referential var--font-sans: var(--font-sans)Use a different name
Override not workingDefined outside @themeMove into @theme
Breakpoint not workingUsed @theme instead of @theme referenceUse 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

ItemValue
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 patternRedefine the variable
Clear pattern--namespace-*: initial
Runtime accessvar(--name)

Key takeaways:

  • @import "tailwindcss"; is the entry point. It replaces the three @tailwind directives from v3. It imports the default theme, the preflight reset, and the utilities. The tailwind.config.js file is no longer the primary configuration method .
  • The @theme directive defines design tokens. Each token is a CSS variable with a namespace prefix. The namespace determines what utility classes are generated. The --color-brand variable generates bg-brand, text-brand, and border-brand .
  • The theme variables are emitted as CSS custom properties. They are available in :root and :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 inline mode inlines values into utilities. It does not emit :root variables. It is used for component library patterns where the utilities reference a base set of CSS variables that change at runtime .
  • The @theme reference mode 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 initial clears 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!