Behold, the <button>
. After Tailwind strips most of the built-in browser styles as part of it’s Preflight. In fact, it barely looks like a button.
<button>Button</button>
We can begin to add a little more to it by using utility classes.
Utility-first CSS prioritizes small, single-purpose classes applied directly to HTML elements instead of semantic classes or IDs. Each utility class does one thing—sets margin, padding, text alignment, or color.
<button class="bg-blue-600 px-3 py-2 text-white">Button</button>
Tailwind pushes the utility-first philosophy further by embracing modern CSS features—cascade layers, OKLCH colors, container queries, and native custom properties—all without leaving HTML.
Advantages
- Rapid Prototyping: Style elements directly in HTML without switching files.
- Reduced Side Effects: Classes scoped to individual elements prevent styling conflicts.
- Reusability: Single-purpose classes work across the entire application.
- Maintainability: Effects of adding/removing classes are predictable and visible in markup.
- Easy to Extend: Frameworks provide customization options for consistent design systems.
Criticisms
- Verbose HTML: Numerous class names can clutter markup.
- Learning Curve: Requires familiarity with utility class names.
- Lack of Semantics: Utility classes don’t convey element meaning (mitigated by combining with semantic classes).
Why It Works
- Predictable cascade: Utilities declared last in the cascade override component and base styles without
!important
. - Design-token synergy: Tokens in
@theme
(--color-primary-500
) are available in every utility. - Rapid iteration: Oxide rebuilds styles in milliseconds.
Best Practices
- Compose in HTML first: Reach for custom CSS only when utilities can’t express the idea clearly.
- Extract repeating patterns: Wrap utilities appearing 3+ times in components or
@apply
. - Use
@apply
sparingly: Selectors in@layer components/base
don’t receive variants. For variant-aware helpers, use@utility
. - Declare tokens in
@theme
: Use CSS variables for spacing, colors, fonts instead of arbitrary values (p-[123px]
).
Managing Long Class Lists
Keep long class lists readable:
- Logical grouping: Order by layout → spacing → typography → color → motion.
- Automatic sorting: Use official Tailwind CSS IntelliSense extension or the Prettier Tailwind plugin to enforce consistent ordering.
- Component extraction: Pull verbose patterns into reusable components to keep markup tidy.
Advanced Variants & State
Tailwind 4 introduces powerful variant primitives:
Variant | Purpose | Example |
---|---|---|
md: | Media query ≥ 768px | md:grid-cols-3 |
hover: | Interaction state | hover:bg-primary-600 |
cq: | Container query | cq:gap-6 |
group-has-[.active] | Style parent when it contains .active | group-has-[.active]:bg-blue-50 |
Combine them for expressive rules like md:group-hover/cell:translate-y-1
.
Performance Considerations
- Oxide build step: Tailwind emits only referenced classes for minimal CSS bundles.
- Avoid runtime JIT in prod: Compile-time is faster with no client-side overhead.
- Mind variant explosion: Each variant combination adds bytes. Prune unused classes.
Knowing When to Write Custom CSS
Use custom CSS when:
- Complex keyframe animations are needed
- Algorithmic values required (e.g., trig‑based transforms).
- Selector logic exceeds what utility variants offer.
Keep such rules small, place them in the correct cascade layer, and document why utilities weren’t sufficient.
Further Reading
- By The Numbers: A Year and Half with Atomic CSS
- No, Utility Classes Aren’t the Same As Inline Styles
Popular Utility-First Frameworks
- Tailwind CSS: Most popular, extensive utilities for layouts to theming.
- Tachyons: Fast-loading, readable, maintainable code.
- Basscss: Lightweight, extensible base utilities.