Tailwind CSS: Components, Tokens, and a Practical Design
이 글의 핵심
Understand how Tailwind behaves inside the PostCSS pipeline (JIT, content scanning), then align design tokens, layers, and production component patterns in one coherent workflow.
Introduction
Without a deliberate Tailwind CSS project structure, piling on @apply and arbitrary hex colors leads to ?�why does this button have three sizes???months later. Even without a formal design system, tokens (color, spacing, type) + a component class layer cuts maintenance cost sharply.
This article covers Tailwind v4 (2026, @import "tailwindcss") and v3-style tailwind.config customization, and connects PostCSS, the JIT engine, and content scanning?�not just folder names?�so you can explain what happens when styles are built and avoid ?�production-only??surprises.
Early sprints favor raw utilities for speed, but missing the moment to introduce tokens and a component layer makes refactors expensive. Below we align when to promote patterns to shared layers and how scanning and tokens interact.
Why tokens and layers?
Tailwind helps you ship UI quickly with utilities. As projects grow, hex codes and spacing numbers scattered in JSX force global search-and-replace for design updates. theme.extend and @layer centralize meaningful names so brand refreshes and dark mode cost less.
Production watchouts
- Dynamic class strings (
'text-' + color) may not be static analysis?�friendly?�expect missing styles in production. Use safelist or a full class-name map. - Too much
@applycan add abstraction without reuse. A team rule (e.g. promote after three repeats of the same five utility lines) helps. - In monorepos, omitting package sources from
contentcauses prod-only missing styles.
After reading
- Know where Tailwind mutates the CSS AST in PostCSS.
- Explain how JIT scans files and generates or omits utility rules.
- Split primitive vs semantic tokens and pair
theme.extendwith CSS variables where appropriate. - Apply variant APIs, presets, and library boundaries with clear criteria.
Table of contents
- Concepts: tokens, primitives, components
- PostCSS plugin architecture and the build pipeline
- JIT compilation: utilities generated on demand
- Not ?�purge?�—content-driven generation and tree-shaking
- Design token system: primitive, semantic, runtime
- Implementation:
tailwind.configand global layers - Production component patterns
- Advanced: plugins, dark mode, component libraries
- Performance:
@applyvs utility composition - Real-world cases
- Troubleshooting
- Wrap-up
1. Concepts: tokens, primitives, components
- Design tokens: meaningful names ??values for palette, spacing scale, radius, font sizes, and line heights.
- Primitives: minimal reusable styles like
btn,input,cardacross screens. - Page/feature components: primitives composed into React/Vue/Svelte??business logic meets style* here.
Tailwind?�s strength is experiment with utilities, then promote stable patterns to tokens and @layer. The sections below show how that promotion becomes reproducible CSS in the build pipeline.
2. PostCSS plugin architecture and the build pipeline
PostCSS parses CSS into an AST, runs a plugin chain in order, and serializes back to CSS. Tailwind is a plugin that understands @tailwind base/components/utilities or v4?�s @import "tailwindcss" and expands directives into the final stylesheet.
Why order matters
Typically you fix the meaning of source CSS first, then attach Autoprefixer, minifiers, etc. Teams differ on whether postcss-import merges files before Tailwind or a single entrypoint suffices, but the invariant is: Tailwind resolves @apply and @layer before downstream tools assume ?�final??rules. Wrong order can break nesting, variable scope, or cause subtle plugin conflicts.
Typical postcss.config (v3-style)
// postcss.config.js ??illustrative; align with your stack
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
In monorepos you may need to point the Tailwind plugin at a specific config file. When multiple configs exist, document which CSS entry maps to which config to avoid ?�works locally, different in CI.??
Tailwind v4 entrypoints and responsibility split
v4 often uses @import "tailwindcss"; in CSS. Internally there is still a pipeline that reads CSS, generates and merges rules, whether PostCSS or Lightning CSS / Vite drives it. The split remains source ??transform ??final asset; teams only need clarity on which tool interprets @apply and custom @theme.
In short: Tailwind is the engine that turns class candidates + config into CSS rules; PostCSS (or an equivalent adapter) places that engine in the same AST world as the rest of your stylesheets.
v4, Vite, and Lightning CSS
Many v4 templates attach @tailwindcss/vite or use Lightning CSS so a hand-written postcss.config is optional. Scan ??generate ??emit is unchanged; document whether your team?�s standard path goes through PostCSS or the Vite plugin so debugging stays predictable.
3. JIT compilation: utilities generated on demand
Older workflows pre-generated huge stylesheets and stripped unused rules. Today?�s default is closer to JIT: at dev and build time, Tailwind collects class candidates from project sources and emits only the utilities you need.
Scan ??collect ??generate
- Walk files matched by
content(v3) or the file set your bundler provides. - Extract strings parseable as Tailwind class syntax. Template literals, concatenation, and conditional rendering can hit static analysis limits?�candidates may be missed.
- For each candidate, apply theme, plugins, and variants (
hover:,dark:, ?? to produce selectors and declarations. - Cache incrementally in dev to keep rebuilds fast.
JIT is not ?�the browser composes classes at runtime?? it is build-time generation. Dynamic combinations that never appear as whole class names in scanned sources may not exist in the output.
v4 and moving configuration
Some options moved to @theme and @import in v4, but the engine still reacts to scan results. When migrating, prioritize scan paths and dynamic-class policy over whether a knob lived in JS or CSS.
4. Not ?�purge?�—content-driven generation and tree-shaking
PurgeCSS-style ?�build everything, delete unused??differs from JIT + content scanning, which avoids emitting most unused rules upfront. The feel is similar to JS tree-shaking: only what you need in the final artifact?�but the mechanism is candidate generation, not only deletion.
content is a whitelist of scan roots
The content array in tailwind.config.js defines where to look for class names. Classes that only exist outside those globs do not get generated. Conversely, if tests, Storybook, or package sources are omitted, styles visible in local stories can vanish in the app production build.
// tailwind.config.js ??monorepo paths often added
module.exports = {
content: [
'./src/**/*.{js,ts,jsx,tsx,astro,vue,svelte}',
'../../packages/ui/src/**/*.{js,ts,tsx}',
],
// ...
};
safelist and full class maps
For runtime-only classes (themes, CMS options), the scanner may never see them. Use safelist, patterns, or explicit maps of full class strings?�a classic cause of *?�broken only in production.??
Terminology
- Legacy ?�purge??*: remove unused rules after a large build.
- Content + JIT: generate needed rules; missing paths surface as missing utilities, not silent success.
Prefer content scanning and candidate generation in team docs over vague ?�purge.??
5. Design token system: primitive, semantic, runtime
Once tokens are structured, components and pages become explainable.
Primitive tokens
Source values: palette steps (brand.500), spacing scale. theme.extend (or @theme) fits build-time constants.
Semantic tokens
Role-based names: surface, muted, danger?�optimize for what it means, not which palette step it is. Helps dark mode and brand switches when only semantic layers change.
Runtime tokens (CSS variables)
For post-deploy theming or per-tenant colors, CSS custom properties are often the single source of truth; Tailwind bridges with bg-[var(--color-surface)]. Document fallbacks and scope (:root vs [data-theme]).
Boundary between tokens and components
Tokens store design decisions; components encode how tokens combine. Tokens without component rules sprawl variants; components without tokens duplicate meaningful-but-different class soups per page. Use both.
6. Implementation
6-1. tailwind.config.js ??centralize tokens with theme.extend
// tailwind.config.js (v3-style example)
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx,astro,vue,svelte}'],
theme: {
extend: {
colors: {
brand: {
50: '#f0f9ff',
500: '#3b82f6',
900: '#1e3a8a',
},
surface: {
DEFAULT: '#ffffff',
muted: '#f4f4f5',
},
},
spacing: {
18: '4.5rem',
22: '5.5rem',
},
borderRadius: {
card: '0.75rem',
},
fontSize: {
display: ['2.25rem', { lineHeight: '2.5rem', fontWeight: '700' }],
},
},
},
plugins: [],
};
Keep primitive palettes like brand.*, but prefer consuming semantic tokens like surface in UI rules so palette updates propagate through semantic mapping.
6-2. Global CSS ??fix layer order
/* src/styles/globals.css ??v4 example */
@import "tailwindcss";
@layer components {
.btn {
@apply inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium
transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2;
}
.btn-primary {
@apply btn bg-brand-500 text-white hover:bg-brand-900 focus-visible:outline-brand-500;
}
.btn-ghost {
@apply btn bg-transparent text-brand-500 hover:bg-brand-50;
}
}
@layer controls cascade order across utilities, components, and pages. If layer order drifts, you get *?�only this component looks wrong?? overrides.
6-3. Usage in components
// Button.tsx
export function Button({
variant = 'primary',
...props
}: React.ButtonHTMLAttributes<HTMLButtonElement> & { variant?: 'primary' | 'ghost' }) {
const cls =
variant === 'primary' ? 'btn-primary' : 'btn-ghost';
return <button type="button" className={cls} {...props} />;
}
Rule of thumb: components/ui/ holds primitives; features/ holds domain components?�keep @apply on primitives; keep page class strings short.
7. Production component patterns
Variant APIs: avoid combinatorial explosion
When size × variant × iconOnly grows, centralize variant tables (the idea behind class-variance-authority). The win is one place owns combination rules, whether or not you adopt a specific library.
const buttonVariants = {
primary: 'btn-primary',
ghost: 'btn-ghost',
} as const;
export function Button({
variant = 'primary',
className,
...props
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: keyof typeof buttonVariants;
}) {
return (
<button
type="button"
className={[buttonVariants[variant], className].filter(Boolean).join(' ')}
{...props}
/>
);
}
Standardize tailwind-merge (or not) for conflicting utilities when parents override children.
Compound components and slots
For cards and dialogs, expose token-based primitive classes on each slot and inject content from parents?�consistency depends on every slot pulling from the same token set.
Design-system packages
For packages/ui, use peerDependencies on tailwindcss, ship tailwind.preset.js, and ensure UI package sources stay inside the app content globs?�otherwise JIT issues recur.
Production checklist
- Include Storybook/doc templates in
contentwhen they introduce classes. - Handle dynamic classes with safelist or explicit maps.
- Prefer semantic tokens for dark mode instead of
dark:sprawl. - Watch for
@applyconflicts with global layer order.
8. Advanced
Dark mode: class strategy + tokens
// tailwind.config.js
module.exports = {
darkMode: 'class',
// ...
};
<div className="bg-surface text-zinc-900 dark:bg-zinc-900 dark:text-zinc-50">
...
</div>
When dark: repeats everywhere, swapping semantic tokens via CSS variables (--color-surface) and a single bg-[var(--color-surface)] often scales better.
Plugins like @tailwindcss/forms
Form resets affect the whole team??agree once* and document interaction with preflight. Plugins inject rules and variants through the same PostCSS chain?�note debug order when conflicts appear.
9. Performance: @apply vs utility composition
| Approach | Pros | Watchouts |
|---|---|---|
| Raw utilities | Easy to trace; JIT-friendly | Verbose markup |
@apply component classes | Cleaner markup; DS-friendly | Abstracting without reuse |
| CSS variables + Tailwind | Runtime theming | Naming and fallback policy |
Rule of thumb: consider @apply when the same five+ utility lines repeat three+ times. Most ?�performance??issues are scan misses or duplicate rules, not micro-optimizations of class count.
10. Real-world cases
- Multi-brand:
data-theme="acme"+ CSS variables for colors; Tailwind usesbg-[var(--color-brand)]. - Monorepo:
tailwind.preset.jsinpackages/ui; apps usepresets: [require('@repo/ui/tailwind.preset')]. - Astro/Next: put all package sources in
contentto avoid missing utilities.
11. Troubleshooting
| Mistake | Result | Fix |
|---|---|---|
| String concatenation for classes | Missing styles in prod | Full class map or safelist |
darkMode: 'class' but no root class | Dark styles never apply | Toggle dark on html or container |
Heavy arbitrary values only in @apply | Build/plugin conflicts | Prefer utilities or simplify |
| Figma vs code drift | Double maintenance | Token pipeline (Figma ??JSON ??theme) |
JIT cannot find a class
Dynamic concatenation is not reliably parsed?�use safelist or complete class names.
Arbitrary values break in @apply
Check plugin order and @layer placement; sometimes direct utilities on the component are safer.
Figma tokens diverge
A Figma ??JSON ??Style Dictionary ??Tailwind theme pipeline reduces drift.
12. Wrap-up
Tailwind project structure is less about fancy folders than a single source of truth for tokens and team-agreed layer rules. Understanding PostCSS position, JIT, and content scanning lets you block production-only missing styles at design time. Pair with HTML/CSS series on animation and responsive layout for end-to-end consistency.
?�주 묻는 질문 (FAQ)
Q. ???�용???�무?�서 ?�제 ?�나??
A. Connect PostCSS, the JIT engine, and content scanning to tokens, layers, and production-ready component patterns in Tail???�무?�서????본문???�제?� ?�택 가?�드�?참고???�용?�면 ?�니??
Q. ?�행?�로 ?�으�?좋�? 글?�?
A. �?글 ?�단???�전 글 ?�는 관??글 링크�??�라가�??�서?��?배울 ???�습?�다. C++ ?�리�?목차?�서 ?�체 ?�름???�인?????�습?�다.
Q. ??깊이 공�??�려�?
A. cppreference?� ?�당 ?�이브러�?공식 문서�?참고?�세?? 글 말�???참고 ?�료 링크???�용?�면 좋습?�다.
같이 보면 좋�? 글 (?��? 링크)
??주제?� ?�결?�는 ?�른 글?�니??
- CSS ?�니메이??| Transition, Animation, Transform
- [반응?????�자??| 미디??쿼리?� 모바??최적??(/blog/html-css-series-07-responsive/)
- JavaScript ?�자???�턴 | ?��??? ?�토�? ?��?�??�턴
- [Tailwind CSS Complete Guide](/en/blog/tailwind-css-complete-guide/
??글?�서 ?�루???�워??(관??검?�어)
Tailwind CSS, Design System, Components, Design Tokens, PostCSS, JIT, Frontend ?�으�?검?�하?�면 ??글???��????�니??