The utility-first mindset
In this chapter, you understand "utility-first," the core idea of Tailwind CSS.
What is a utility class
A utility class is a class that applies a single CSS property.
<!-- Each class is responsible for one property -->
<div class="flex items-center justify-between p-4 bg-white rounded-lg shadow-sm">
...
</div>
| Class | Corresponding CSS |
|---|---|
flex | display: flex |
items-center | align-items: center |
justify-between | justify-content: space-between |
p-4 | padding: 1rem |
bg-white | background-color: #fff |
rounded-lg | border-radius: 0.5rem |
shadow-sm | box-shadow: ... |
Note that v3's shadow was renamed to shadow-sm in v4 (the shadow scale is shifted by one step).
Comparison with traditional CSS
The traditional approach
.card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
background-color: white;
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
<div class="card">...</div>
Utility-first
<div class="flex items-center justify-between p-4 bg-white rounded-lg shadow-sm">
...
</div>
Why utility-first
1. You do not have to think about class names
With traditional CSS, it takes time to come up with appropriate class names.
/* What should I name this? */
.card-wrapper { }
.card-container { }
.card-outer { }
.product-card-layout { }
With utility classes, you just write the styles directly.
2. The CSS does not bloat
With the traditional approach, the CSS keeps growing as the project progresses.
/* Similar styles proliferate */
.card { padding: 1rem; }
.card-large { padding: 1.5rem; }
.card-small { padding: 0.5rem; }
.product-card { padding: 1rem; }
.user-card { padding: 1rem; }
With Tailwind CSS, only the classes you use are emitted.
3. Changes are safe
With traditional CSS, changing one class can affect other places.
/* Is this change safe? */
.card {
padding: 1rem; /* → what happens if I change it to 2rem... */
}
Utility classes are scoped to each element, so the scope of impact is clear.
4. Enforcing a design system
Because Tailwind CSS uses predefined values, you can maintain a consistent design.
<!-- Choose from a fixed scale -->
<div class="p-1">4px</div>
<div class="p-2">8px</div>
<div class="p-4">16px</div>
<div class="p-8">32px</div>
<!-- Arbitrary values are also possible, but not recommended -->
<div class="p-[17px]">17px</div>
Arbitrary values (p-[17px]) are convenient, but they cause your design consistency to break down. First consider whether you can handle it with Tailwind's scale (p-4 = 16px, p-5 = 20px). In v4 you can specify an integer that is not on the scale even without brackets, as in p-17, but note that p-17 becomes 17 × 0.25rem = 4.25rem (not 17px). Either way, overusing off-scale values breaks design consistency.
Naming convention
Tailwind CSS class names follow an intuitive convention.
Pattern 1: property-value
<div class="text-center"> <!-- text-align: center -->
<div class="font-bold"> <!-- font-weight: bold -->
<div class="bg-blue-500"> <!-- background-color: ... -->
Pattern 2: property-direction-value
<div class="p-4"> <!-- padding: 1rem (all sides) -->
<div class="px-4"> <!-- padding-left/right: 1rem -->
<div class="pt-4"> <!-- padding-top: 1rem -->
<div class="mt-4"> <!-- margin-top: 1rem -->
Pattern 3: condition-property-value
<div class="hover:bg-blue-600"> <!-- on hover -->
<div class="md:flex"> <!-- 768px and up -->
<div class="dark:bg-gray-800"> <!-- dark mode -->
The spacing scale
Tailwind CSS uses a 4px-based scale.
| Class | Value |
|---|---|
0 | 0px |
1 | 4px (0.25rem) |
2 | 8px (0.5rem) |
3 | 12px (0.75rem) |
4 | 16px (1rem) |
6 | 24px (1.5rem) |
8 | 32px (2rem) |
12 | 48px (3rem) |
16 | 64px (4rem) |
The "HTML gets long" problem
When you use utility classes, the HTML can get long.
<button class="inline-flex items-center justify-center px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md shadow-sm hover:bg-blue-700 focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Button
</button>
Solution 1: Componentization
function Button({ children }: { children: React.ReactNode }) {
return (
<button className="inline-flex items-center justify-center px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md shadow-sm hover:bg-blue-700 focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
{children}
</button>
)
}
// Usage
<Button>Click</Button>
Solution 2: The @apply directive
/* Use sparingly. You can use it when necessary */
.btn-primary {
@apply inline-flex items-center justify-center px-4 py-2;
@apply text-sm font-medium text-white;
@apply bg-blue-600 hover:bg-blue-700;
@apply rounded-md shadow-sm;
}
Overusing @apply undermines the benefits of utility-first, so prefer componentization (Solution 1). When you need a component-level abstraction, also consider the @utility directive introduced in Chapter 16 / Customization.
In v4, @apply has a file-scope constraint. When you use it outside the main CSS file (in CSS Modules or in the <style> block of Vue/Svelte, etc.), you need to reference the file that defines the theme with the @reference directive.
/* When using @apply in a separate file */
@reference "../../styles/main.css";
.my-component {
@apply text-blue-500;
}
Summary
- A utility class applies a single CSS property
- You do not think about class names, and you prevent CSS bloat
- Changes are safe, and you maintain design consistency
- The regular naming makes it easy to learn
- Solve long class lists with componentization
What to read next
- Flexbox layout — combine utilities to build a layout
- Customization — define your own utilities with
@themeand@utility - Tailwind CSS Guide table of contents — back to the structure of all 18 chapters