Style Dictionary v4 × Tailwind v4: A Single-Source Pipeline That Automatically Propagates Design Tokens to Utility Classes
Target audience: This article is written for frontend developers who use Tailwind CSS in production and struggle with managing design tokens such as colors, spacing, and typography. A basic understanding of CSS variables and build tools will help you follow along more easily.
What this article covers / does not cover
✅ How to output DTCG-format tokens as
@layer tokensCSS using Style Dictionary v4
✅ Integration patterns for Tailwind v4@theme,@theme inline, and@utility
✅ Dark mode token layer separation pattern
✅ Full pipeline from Tokens Studio (Figma) → Tailwind v4
❌ Style Dictionary basics (refer to the official documentation)
❌ Basic Tailwind CSS usage
❌ Figma and Tokens Studio plugin installation and configuration
When operating a design system, you often encounter a situation where color values from Figma are scattered separately across CSS variables, Tailwind configuration, and sometimes JS constants. When someone changes one place, the others fall out of sync. As desynchronization accumulates across colors, spacing, typography, and shadow tokens, you're left with nothing but the question: "Where are the tokens supposed to live?"
By the end of this article, you'll be able to build a single-source pipeline yourself where a single pnpm build propagates Figma colors all the way to the bg-brand-primary Tailwind utility class. Tailwind v4, officially released in January 2025, declares tokens using the CSS @theme directive instead of a JavaScript config file. Style Dictionary v4 has also been reborn with native ESM and DTCG support, causing both tools to converge on a common language: CSS variables. Thanks to this convergence, the pipeline in this article can be implemented without any extra plugins.
Core Concepts
The Full Pipeline Flow
Understanding the overall data flow before reading the article will make the role of each step clear.
tokens/color.json sd.config.js dist/tokens.css globals.css
(DTCG JSON tokens) → (Style Dictionary v4) → (@layer tokens output) → (@theme inline connection)
↓
bg-brand-primary etc.
Tailwind utilities auto-generatedThree core technologies each handle a single responsibility.
| Technology | Responsibility |
|---|---|
| Style Dictionary v4 | Converts JSON tokens → CSS variables (build time) |
CSS @layer |
Isolates priority between token variables and Tailwind's internal layers |
Tailwind v4 @theme |
Auto-generates utility classes from CSS variables |
Style Dictionary v4: A Build System That Converts JSON Tokens to CSS Variables
Style Dictionary is a build system that takes design tokens defined in JSON and converts them into platform-specific code for CSS, iOS, Android, and more. In v4, the entire codebase was rewritten in ESM and natively supports the W3C DTCG spec.
// tokens/color.json (DTCG format)
{
"color": {
"brand": {
"primary": { "$value": "#0055FF", "$type": "color" },
"secondary": { "$value": "#00C2A8", "$type": "color" }
}
}
}DTCG (Design Tokens Community Group) — A W3C community group that defines the specification for standardizing the JSON representation of design tokens. The
$valueand$typekey format is central to the spec, and Style Dictionary v4 recognizes this format natively.
CSS Cascade Layers (@layer): Explicitly Layering Priority
@layer is a feature that declaratively controls the order in which CSS rules are applied. Even rules with the same selector are prioritized based on the order in which layers are declared, preventing unpredictable specificity conflicts.
/* Layer order declaration: layers declared later have higher priority */
@layer tokens, base, components, utilities;Tailwind v4 internally adopts @layer theme, base, components, utilities as its official layer stack. The reason for isolating Style Dictionary tokens in a separate @layer tokens is to prevent token variable definitions from being unintentionally overridden by Tailwind utility rules. Declaring the tokens layer first gives it the lowest priority, so tokens always serve only as "base values."
Tailwind CSS v4: CSS-First Token Declaration and the @theme Directive
The biggest change in Tailwind v4 is replacing tailwind.config.js with a CSS file. Declaring CSS variables inside a @theme block causes Tailwind to automatically generate utility classes.
@theme {
--color-brand-primary: #0055ff;
/* → bg-brand-primary, text-brand-primary, border-brand-primary auto-generated */
}
@theme inline— With regular@theme, Tailwind copies declared variables internally with a--tw-*prefix. Using@theme inlineskips this copy and references the original variable directly. This is the appropriate approach when registering externally declared CSS variables as Tailwind tokens.
Practical Application
Example 1: Basic Integration — Style Dictionary → @layer tokens → Tailwind @theme
Strategy for Preventing Variable Name Conflicts
There is a problem that must be solved first. If Style Dictionary outputs --color-brand-primary and @theme references it directly as --color-brand-primary: var(--color-brand-primary), a CSS circular reference occurs and the value is invalidated.
The solution is to assign a prefix to SD output variables to differentiate their names. Structuring the top-level key of the token JSON as token will automatically produce output in the form --token-color-brand-primary.
// tokens/color.json — top-level key 'token' automatically adds a prefix
{
"token": {
"color": {
"brand": {
"primary": { "$value": "#0055FF", "$type": "color" },
"secondary": { "$value": "#00C2A8", "$type": "color" }
}
},
"spacing": {
"4": { "$value": "1rem", "$type": "dimension" }
},
"font-size": {
"lg": { "$value": "1.125rem", "$type": "dimension" }
}
}
}Step 1: Style Dictionary Configuration — @layer tokens Wrapping Format
The default css/variables format does not support @layer wrapping, so the most reliable approach is to wrap it manually with a custom format.
// sd.config.js
import StyleDictionary from 'style-dictionary';
// Custom format that wraps output in @layer tokens
StyleDictionary.registerFormat({
name: 'css/layer-tokens',
format: ({ dictionary, options }) => {
const selector = options.selector ?? ':root';
const vars = dictionary.allTokens
.map(token => ` ${token.name}: ${token.value};`)
.join('\n');
return `@layer tokens {\n ${selector} {\n${vars}\n }\n}\n`;
},
});
export default {
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'css',
prefix: 'token',
files: [
{
destination: 'dist/tokens.css',
format: 'css/layer-tokens',
options: {
selector: ':root',
outputReferences: true,
},
},
],
},
},
};
outputReferences: true— When a token references another token (like"$value": "{color.brand.primary}"), instead of copying the value directly, it outputs a CSS variable reference in the formvar(--token-color-brand-primary). This keeps the connection between tokens alive at runtime, enabling dynamic theme switching.
Output Result (dist/tokens.css)
@layer tokens {
:root {
--token-color-brand-primary: #0055ff;
--token-color-brand-secondary: #00c2a8;
--token-spacing-4: 1rem;
--token-font-size-lg: 1.125rem;
}
}Step 2: Connect from the Tailwind CSS Entry Point
/* src/globals.css */
@import "tailwindcss";
@import "../dist/tokens.css";
/* Explicit layer priority declaration (always at the top) */
@layer tokens, base, components, utilities;
@theme inline {
/* SD prefixed variables → mapped to Tailwind tokens (no circular reference) */
--color-brand-primary: var(--token-color-brand-primary);
--color-brand-secondary: var(--token-color-brand-secondary);
--spacing-4: var(--token-spacing-4);
--font-size-lg: var(--token-font-size-lg);
}| Syntax | Role |
|---|---|
@import "tailwindcss" |
Injects the full Tailwind v4 layer stack |
@import "../dist/tokens.css" |
Loads Style Dictionary output CSS variables |
@layer tokens, base, components, utilities |
Declares layer priority (tokens is lowest) |
@theme inline |
Registers original CSS variables as Tailwind tokens without copying |
With this configuration alone, Tailwind utility classes like bg-brand-primary, text-brand-secondary, and p-4 become automatically available.
Step 3: Connect the package.json Build Script
// package.json
{
"scripts": {
"build:tokens": "style-dictionary build --config sd.config.js",
"build:css": "tailwindcss -i src/globals.css -o dist/output.css",
"build": "pnpm build:tokens && pnpm build:css"
}
}A single pnpm build runs token building → CSS generation in sequence.
Example 2: Dark Mode Token Layer Separation Pattern
Separating light/dark mode tokens by selector within the same @layer tokens integrates naturally with Tailwind's dark: variant.
Style Dictionary Configuration
// sd.config.js — separate output for light/dark files
StyleDictionary.registerFormat({
name: 'css/layer-tokens',
format: ({ dictionary, options }) => {
const selector = options.selector ?? ':root';
const vars = dictionary.allTokens
.map(token => ` ${token.name}: ${token.value};`)
.join('\n');
return `@layer tokens {\n ${selector} {\n${vars}\n }\n}\n`;
},
});
export default {
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'css',
prefix: 'token',
files: [
{
destination: 'dist/tokens.css',
format: 'css/layer-tokens',
filter: token => !token.filePath.includes('dark'),
options: { selector: ':root' },
},
{
destination: 'dist/tokens-dark.css',
format: 'css/layer-tokens',
filter: token => token.filePath.includes('dark'),
options: { selector: '[data-theme="dark"]' },
},
],
},
},
};Output Result
/* dist/tokens.css */
@layer tokens {
:root {
--token-color-bg: #ffffff;
--token-color-text: #111111;
}
}/* dist/tokens-dark.css */
@layer tokens {
[data-theme="dark"] {
--token-color-bg: #111111;
--token-color-text: #f5f5f5;
}
}Tailwind Connection
/* src/globals.css */
@import "tailwindcss";
@import "../dist/tokens.css";
@import "../dist/tokens-dark.css";
@layer tokens, base, components, utilities;
@theme inline {
--color-bg: var(--token-color-bg);
--color-text: var(--token-color-text);
}Toggling the data-theme="dark" attribute on the <html> tag immediately swaps out the CSS variables, and you can also read the current theme colors from JS via getComputedStyle(), making this approach useful for dynamic theme switching.
Example 3: Writing Token-Based Custom Utilities with @utility
Using Tailwind v4's @utility directive, you can create token-based custom utilities at the CSS level without JS plugins.
/* src/globals.css */
@utility btn-primary {
background-color: var(--token-color-brand-primary);
color: var(--token-color-white);
padding: var(--token-spacing-2) var(--token-spacing-4);
border-radius: var(--token-radius-md);
font-weight: 600;
}
@utility btn-secondary {
background-color: var(--token-color-brand-secondary);
color: var(--token-color-white);
padding: var(--token-spacing-2) var(--token-spacing-4);
border-radius: var(--token-radius-md);
}All of Tailwind's variants are automatically supported, like hover:btn-primary, dark:btn-primary, and focus:btn-secondary. This approach has less performance overhead than @apply and is the method recommended by the Tailwind v4 team.
Example 4: Full Pipeline — Tokens Studio → Style Dictionary v4 → Tailwind v4 (Optional Deep Dive)
This example is advanced content for teams using the Figma + Tokens Studio plugin. If you don't use Figma, Examples 1–3 are sufficient.
Adding the @tokens-studio/sd-transforms package allows Style Dictionary to process Figma-exported JSON directly.
pnpm add -D style-dictionary @tokens-studio/sd-transforms// sd.config.js — full Tokens Studio pipeline
import StyleDictionary from 'style-dictionary';
import { registerTransforms } from '@tokens-studio/sd-transforms';
// Register Tokens Studio preprocessing transforms on the StyleDictionary instance
registerTransforms(StyleDictionary);
StyleDictionary.registerFormat({
name: 'css/layer-tokens',
format: ({ dictionary, options }) => {
const selector = options.selector ?? ':root';
const vars = dictionary.allTokens
.map(token => ` ${token.name}: ${token.value};`)
.join('\n');
return `@layer tokens {\n ${selector} {\n${vars}\n }\n}\n`;
},
});
export default {
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'tokens-studio',
prefix: 'token',
files: [
{
destination: 'dist/tokens.css',
format: 'css/layer-tokens',
options: {
selector: ':root',
outputReferences: true,
},
},
],
},
},
};The tokens-studio/sd-tailwindv4 reference implementation (GitHub) is the official example for this pipeline. The entire process — from Figma → JSON export → CSS variables → Tailwind utility classes — is completed with a single pnpm build.
※ Related post already written: "Automating Multi-Brand × Dark Mode Design Tokens in a Single Pipeline with Tokens Studio — A Complete Guide to Style Dictionary and permutateThemes"
Pros and Cons
Advantages
| Item | Details |
|---|---|
| Single-source management | Modifying one token JSON automatically updates CSS variables, Tailwind utilities, and JS constants |
| Cascade predictability | Thanks to @layer, specificity conflicts where token variables get overwritten by utilities disappear |
| Runtime accessibility | CSS variables can be read from JS via getComputedStyle(), making dynamic theme switching simple |
| Framework independence | Style Dictionary output can be reused in frameworks other than Tailwind or in vanilla CSS |
@property type safety |
Registering types for CSS variables improves the predictability of transition and animation interpolation |
Disadvantages and Caveats
| Item | Details | Mitigation |
|---|---|---|
| Initial setup complexity | SD configuration, format customization, and Tailwind @theme mapping overlap, creating a steep learning curve |
It is recommended to use the tokens-studio/sd-tailwindv4 reference implementation as a starter |
| Variable naming strategy required | If SD output variable names and Tailwind token variable names are identical, circular reference errors occur | Differentiate variable names using SD prefix configuration or top-level key naming |
Risk of @apply overuse |
The Tailwind v4 team officially warns against overusing @apply |
It is recommended to switch to the @utility directive and direct CSS variable usage |
| Build pipeline dependency | A CI pipeline connection is required for Figma changes → SD build → Tailwind regeneration | Adding a pnpm build step to CI such as GitHub Actions automates this |
| Layer order management | If the @layer order declaration is missing, priority can vary based on import order |
It is recommended to always explicitly declare @layer tokens, base, components, utilities; at the top of the CSS file |
| Browser support | @layer is supported from Chrome 99+, Firefox 97+, Safari 15.4+ |
Difficult to apply to projects requiring IE support |
@property— A CSS rule that registers the type, initial value, and inheritance behavior of a CSS variable with the browser. Declaring it like@property --color-brand-primary { syntax: '<color>'; inherits: true; initial-value: #0055ff; }ensures the variable is correctly interpolated in transitions and animations.
The Most Common Mistakes in Practice
-
Using the same name for SD output variables and Tailwind token variables —
--color-brand-primary: var(--color-brand-primary)is treated as a CSS circular reference and the value is invalidated. It is essential to differentiate names like--token-color-brand-primaryusing SD prefix configuration or top-level key structure. -
Not declaring the
@layerorder at the top of the CSS file — Without a layer order declaration, the priority is determined by the order each@layerblock first appears, causing styles to vary based on import order. It is strongly recommended to always declare@layer tokens, base, components, utilities;at the top ofglobals.css. -
Outputting Style Dictionary variable names in camelCase — If SD outputs
--color-brandPrimarydue to default settings, Tailwind generates abg-brandPrimaryclass, breaking the naming convention. UsetransformGroup: 'css'in the SD configuration or explicitly apply thename/kebabtransform to maintain kebab-case (bg-brand-primary).
Closing Thoughts
The combination of Style Dictionary v4's @layer tokens output and Tailwind v4's @theme inline integration enables a truly single-source pipeline where a single change to a JSON token propagates simultaneously to CSS variables and utility classes.
Three steps you can start right now:
-
Run
pnpm add -D style-dictionary, then try moving a few of your project's existing color tokens intotokens/color.jsonin DTCG format ($value,$type). Add a"build:tokens": "style-dictionary build --config sd.config.js"script topackage.jsonand verify your first CSS variable output withpnpm build:tokens. -
Add the output
dist/tokens.csstoglobals.cssvia@import, then write the@theme inlineblock from Example 1 (mapping SD prefixed variables → Tailwind tokens). You can confirm in the browser DevTools Elements tab that thebg-brand-primaryclass is being auto-generated. -
If your team uses Figma, it is recommended to add
pnpm add -D @tokens-studio/sd-transformsand connect Tokens Studio JSON directly to the pipeline. Using thetokens-studio/sd-tailwindv4GitHub reference implementation as a starter template can significantly reduce setup time.
Next article: How to register types for design tokens with CSS
@propertyand safely control transitions and animations — a practical guide for Style Dictionary v4 and Tailwind v4
References
- Style Dictionary Official Documentation | styledictionary.com
- Style Dictionary v4 Migration Guidelines | styledictionary.com
- Style Dictionary Built-in Formats | styledictionary.com
- tokens-studio/sd-tailwindv4 Reference Implementation | GitHub
- tokens-studio/sd-transforms | GitHub
- style-dictionary-utils | npm
- Tailwind CSS v4.0 Official Release Notes | tailwindcss.com
- Tailwind CSS Functions and Directives | tailwindcss.com
- Tailwind CSS Adding Custom Styles | tailwindcss.com
- Using CSS Cascade Layers With Tailwind Utilities | CSS-Tricks
- The CSS / Utility hybrid approach with Tailwind v4 | This Dot Labs
- Design Tokens That Scale in 2026 (Tailwind v4 + CSS Variables) | Mavik Labs