How to organize your Tailwind styles without going crazy: design tokens, reusable components and scalable patterns.
The Scaling Problem
Tailwind is incredibly productive at first. But in large projects, without a clear strategy, you end up with classes repeated in 30 places and a button with 15 inconsistently defined variants.
The solution isn’t to abandon Tailwind — it’s to give it structure.
Design Tokens as Source of Truth
Before writing a single class, define your tokens in tailwind.config.js:
// tailwind.config.js
export default {
theme: {
extend: {
colors: {
brand: {
DEFAULT: '#f97316',
hover: '#ea580c',
muted: 'rgb(0 255 136 / 0.1)',
},
surface: {
base: '#0a0a0a',
raised: '#111111',
overlay: '#161616',
},
border: {
subtle: '#1f1f1f',
default: '#2a2a2a',
strong: '#333333',
},
},
fontFamily: {
mono: ['GeistMono', 'Fira Code', 'monospace'],
},
},
},
};
Now instead of bg-[#f97316] in 50 places, you use bg-brand. If the color changes, you change one place.
Components with @apply
For highly repeated elements, @apply in your CSS is a valid solution:
/* components.css */
@layer components {
.btn-primary {
@apply inline-flex items-center gap-2 px-5 py-2.5
bg-brand text-black font-semibold text-sm rounded-lg
hover:bg-brand-hover transition-colors;
}
.card {
@apply rounded-xl border border-border-subtle
bg-surface-raised/50 hover:bg-surface-raised
transition-all duration-200;
}
}
Use it in moderation — the goal is to extract really repeated patterns, not every button you see.
Variant Convention
Define consistent variants for interactive elements:
Base state: text-[#888] border-[#1f1f1f] bg-[#111]/50
Hover state: text-[#f0f0f0] border-[#333] bg-[#111]
Active state: text-[#f97316] border-[#f97316]/20
Disabled state: text-[#333] cursor-not-allowed opacity-50
Documenting this in a DESIGN.md file or your Storybook prevents inconsistencies between components.
File Organization
src/
styles/
global.css → @tailwind directives, @font-face, reset
components.css → @apply for heavily repeated patterns
components/
ui/
Button.tsx → Variants with class-variance-authority
Card.tsx
Badge.tsx
class-variance-authority (CVA)
For components with variants in React/Vue, CVA is superior to concatenating strings:
import { cva } from 'class-variance-authority';
const button = cva(
'inline-flex items-center gap-2 font-semibold rounded-lg transition-colors',
{
variants: {
variant: {
primary: 'bg-[#f97316] text-black hover:bg-[#ea580c]',
ghost: 'text-[#888] border border-[#1f1f1f] hover:text-[#f0f0f0] hover:border-[#333]',
},
size: {
sm: 'text-xs px-3 py-1.5',
md: 'text-sm px-5 py-2.5',
},
},
defaultVariants: { variant: 'primary', size: 'md' },
}
);
Conclusion
Tailwind scales well when you have well-defined tokens, clear state conventions, and extract abstractions only when there’s real repetition. The key is that design decisions live in the config, not scattered throughout the markup.