Tailwind CSS has won the styling war, but using it at scale across a team requires more than just adding class names. Here’s how we build design systems with Tailwind that stay maintainable as projects grow.
The Problem with Utility Classes at Scale
Without conventions, Tailwind projects become a mess of duplicated class strings, inconsistent spacing, and components that look similar but use slightly different values. The solution isn’t to abandon utilities — it’s to build a layer of abstraction on top.
Design Tokens via Tailwind Config
Your tailwind.config.js is your design system’s source of truth. Define every color, spacing value, font size, and shadow as a named token. Never use arbitrary values in production code.
We extend the theme with semantic names: navy, teal, slate for our brand colors. section spacing for consistent vertical rhythm. prose-lg for body text sizes. These tokens encode design decisions so developers don’t have to make them repeatedly.
Component Extraction Strategy
Not everything should be a reusable component. We follow a simple rule: extract when you see the same pattern three times. Before that, inline utilities are fine. After that, create a component with proper TypeScript props.
For design system primitives (buttons, inputs, cards), we use a variant-based approach with cva (class-variance-authority). Define variants for size, color, and state, then compose them into predictable class combinations.
The @apply Question
We use @apply sparingly and only in specific scenarios: base styles for third-party component overrides, global resets, and typography styles for content from CMS. For everything else, utility classes directly in templates provide better discoverability and easier refactoring.
Responsive Design Conventions
Mobile-first always. We define breakpoints once in the config and use them consistently. Every component is built for mobile first, then enhanced at larger breakpoints. Our PR reviews specifically check responsive behavior.
Dark Mode Without the Pain
Using CSS custom properties for colors and Tailwind’s dark mode support makes theming straightforward. Define colors as variables, reference them in the Tailwind config, and toggle them with a class on the root element. No duplicate class strings, no maintenance headache.
Performance at Scale
Tailwind’s JIT compiler generates only the classes you use, so bundle size stays small regardless of how many utilities are available. We run PurgeCSS in production builds and consistently achieve CSS payloads under 15KB gzipped, even on large applications.
Team Conventions That Work
We document class ordering conventions (layout → sizing → spacing → typography → visual), naming patterns for custom utilities, and when to create components vs. use utilities directly. This keeps the codebase consistent regardless of who writes the code.
Need help with your project?
Our team specializes in building production-grade software. Explore our services:
Get engineering insights in your inbox
Production-tested approaches to AI, Laravel, React and more. No spam, unsubscribe anytime.

