The new fast engine (Oxide) is up to 5× faster on a full build and over 100× faster on an incremental build. But its true value is less in speed than in the point that you can make CSS the single source of truth of the design system.
1. v3 → v4: what changed
The biggest change is where configuration lives. v3 wrote colors and breakpoints in a JS config file, but v4 completes inside CSS.
/* ❌ v3 の書き方(v4 では誤り) */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* ✅ v4 の正しい書き方:これ1行 */
@import "tailwindcss";
@plugin "@tailwindcss/typography"; /* プラグインも CSS で読み込む */
In addition, v4 now ships the following as standard.
- Automatic content detection. Manual configuration of the
contentarray is in principle no longer needed. - container queries are built in (no plugin needed). With
@containerand@sm:, etc., you can write styles responsive to the parent element's size. - A modern CSS foundation. Leverages cascade layers,
@property, andcolor-mix(). Also supports@starting-style(entry animation), thenot-*variant,field-sizing,inert, etc.
With configuration gone from JS, design tokens are consolidated into "the single place of CSS," reducing the gap between design and implementation.
2. @theme: manage design tokens centrally
Variables defined in @theme are not mere CSS variables but determine "which utility classes are generated." For example, define --color-primary and bg-primary / text-primary / border-primary become automatically usable.
@theme {
/* 角丸トークン */
--radius-sm: 0.375rem;
--radius: 0.625rem;
--radius-lg: 0.875rem;
/* 影トークン(2層で奥行きを出す) */
--shadow-sm: 0 1px 2px -1px rgb(16 24 40 / 0.06),
0 1px 3px 0 rgb(16 24 40 / 0.05);
}
Now rounded-lg and shadow-sm run on "your design definitions." Instead of hardcoding colors and spacing into each component, reference tokens — this is the heart of ETC (easy to change). A spec change ripples to every screen with a one-spot token edit.
3. The dark-mode pitfall: @theme inline breaks switching
This is the most valuable practical insight of this article. When implementing dark mode with CSS variables in Tailwind v4, using @theme inline makes dark mode stop working.
The reason is this. @theme inline bakes the variable's value into the utility at build time. That is, bg-background is fixed as background-color: hsl(0 0% 100%) (the light value), and doesn't change even when the .dark class is added at runtime.
The correct answer is a two-stage setup: "put the raw channel values in :root / .dark and map to tokens with @theme (non-inline)."
@custom-variant dark (&:where(.dark, .dark *));
/* ① 生の HSL チャンネル値を単一の真実として持つ */
:root {
--background: 0 0% 100%;
--foreground: 222 47% 11%;
--primary: 222 47% 11%;
--primary-foreground: 210 20% 98%;
}
.dark {
--background: 222 47% 5%;
--foreground: 210 20% 98%;
--primary: 210 20% 98%;
--primary-foreground: 222 47% 11%;
}
/* ② 非 inline の @theme でトークンへ。これは
`--color-*: hsl(var(--channel))` を「本物の CSS 変数」として出力するため、
var(--channel) がランタイムに解決され、ライト/ダークが正しく切り替わる。 */
@theme {
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
}
The point is to hold colors as "an HSL channel triplet (222 47% 11%)." In the form hsl(var(--token) / <alpha>), you can append arbitrary transparency afterward (e.g., bg-primary/15). Choose @theme inline and this "runtime resolution" is lost and dark mode dies — remember this one point without fail.
4. Fluid typography: bundle 4 attributes in one utility
Writing a heading's size per breakpoint as text-2xl md:text-4xl lg:text-6xl is verbose. With clamp() and @theme's modifier-bearing tokens, you can apply font-size, line-height, letter-spacing, and font-weight together with a single text-display-2xl.
@theme {
--text-display-2xl: clamp(2.75rem, 1.6rem + 5.1vw, 5.5rem);
--text-display-2xl--line-height: 1.08;
--text-display-2xl--letter-spacing: -0.02em;
--text-display-2xl--font-weight: 700;
}
// 1クラスで「滑らかに伸縮する見出し」が完成。改行制御は text-balance を併用
<h1 className="text-display-2xl text-balance">見出し</h1>
For numeric display, applying tabular-nums (monospaced digits) aligns the columns and avoids jitter in count-ups and the like. It's a small difference, but it's the accumulation of refined UI.
5. Build accessibility into CSS
Not "a11y later" but built in at the token-design stage. Even in Tailwind v4, plain CSS media queries are usable as-is.
@layer base {
/* キーボード操作時だけフォーカスリングを見せる */
:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: 2px;
}
/* アニメーションは prefers-reduced-motion を尊重 */
@media (prefers-reduced-motion: reduce) {
*,
::before,
::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/* スクロールバー出現での横ズレ(CLS)を防ぐ */
html {
scrollbar-gutter: stable;
}
}
/* ハイコントラスト設定では、細い罫線と淡色テキストを濃くする */
@media (prefers-contrast: more) {
:root {
--border: 222 30% 35%;
--muted-foreground: 222 25% 28%;
}
}
/* Windows ハイコントラスト(forced-colors)でもフォーカスを維持 */
@media (forced-colors: active) {
:focus-visible {
outline-color: Highlight;
}
}
When deciding color tokens, satisfy contrast ratio AA (body 4.5:1, large text 3:1). Light text (muted-foreground) tends to lose on cards, so choose values that secure 4.5:1 not only on white backgrounds but also on "lightly colored surfaces." For the big picture of accessibility, see the WCAG 2.2 implementation guide.
6. container queries: truly reusable components
Media queries branch by "screen width," but container queries branch by "the parent element's width." You can place the same card in both a sidebar and the main area, each optimized for its width. This is true reusability (ETC).
<div className="@container">
{/* 親が広いときだけ横並びに */}
<article className="flex flex-col @md:flex-row @md:gap-6">
<img className="aspect-video @md:w-48" />
<div>...</div>
</article>
</div>
Thinking in "this placement is wide" rather than "the screen is wide" makes the component independent of its placement context and stronger as a part of the design system.
7. When to use custom utilities and @apply
Repeating patterns can be carved out into custom utilities. But don't overuse them (YAGNI).
/* 自前ユーティリティ:本当に何度も使うものだけ */
@utility container-tight {
margin-inline: auto;
max-width: 48rem;
padding-inline: 1rem;
}
@apply tempts you with "the urge to bundle existing classes," but overusing it loses the benefit of utility-first (you can tell by looking at the HTML). The principle is to line up utilities on the HTML side. Limit @apply to design-system primitives (buttons, etc.) and avoid it in app screens.
8. Performance and cost efficiency
| Item | Handling in v4 | Effect |
|---|---|---|
| Build speed | Oxide engine (Rust) | full up to 5×, incremental over 100× |
| Unused CSS | automatic content detection + tree-shaking | production CSS is small, favorable to LCP |
| Delivery size | token = CSS variable reduces duplication | optimizes cache efficiency and transfer volume |
| Runtime | static CSS at build time | zero JS execution cost (favorable to INP) |
CSS being small and static directly leads to Core Web Vitals improvement (details in the Core Web Vitals optimization guide). "A fast site is a cheap site" too, helping reduce delivery cost.
9. Testability / observability
- Automatic contrast inspection. Build
@axe-core/playwrightinto CI to detect whether a token change introduces insufficient contrast. Because changing a token changes the colors of every screen, automatic guards work. - Visual regression. With snapshot comparison, visualize the impact range of a token change.
- Test both dark and light. Take screenshots in both modes to check that chapter 3's design isn't broken.
10. Anti-patterns
- ❌ Defining colors with
@theme inlineand dark mode dies. Use non-inline + raw CSS variables for runtime switching (chapter 3). - ❌ Scattering arbitrary values (
bg-[#1a2b3c]) everywhere. Tokenize tobg-primary. The single source of truth of color collapses. - ❌ Removing focus with
:focus { outline: none }. Show it with:focus-visible. - ❌ Ignoring
prefers-reduced-motion/forced-colors. They're essentials that can be handled in a few lines of CSS. - ❌ Overusing
@applyin app screens. Limit it to primitives and build screens with utilities. - ❌ Building parts with media queries alone. Use container queries for parts that don't depend on the placement context.
11. FAQ (frequently asked questions)
Q. Should I migrate from v3 to v4?
A. For a new project, v4 is the only choice. For existing, there's an official upgrade tool, but because it requires replacing @tailwind directives, moving config to CSS, and changing plugin loading, migrate after confirming the impact range.
Q. Can I no longer use tailwind.config.js?
A. CSS-first is the standard, but loading a JS config for compatibility is also possible. For new work, the CSS @theme is recommended (since it can be consolidated into a single place).
Q. Dark mode doesn't switch.
A. The leading cause is color definition with @theme inline. Put raw CSS variables in :root/.dark and map with non-inline @theme (chapter 3).
Q. Can I use it with shadcn/ui? A. The compatibility is excellent. shadcn assumes a CSS-variable-based theme and meshes directly with this article's token design (shadcn/ui design guide).
Q. How finely should I split design tokens?
A. Split by "meaning" (primary / muted / destructive). Naming by role rather than the color itself (blue-500) makes a rebrand or color-scheme change one-spot.
Conclusion: make CSS the single source of truth of the design system
The essence of Tailwind v4 is not speed but consolidation of design. Gather color, typography, radius, shadow, and breakpoints in @theme, generate utilities from there, and manage a11y, contrast, and dark mode in the same place — this becomes the foundation of a design system that doesn't break down.
- Unify into a CSS-first configuration with
@import+@theme. - Make colors support runtime/dark mode with raw channels + non-inline
@theme. - Build placement-context-resilient parts with fluid typography and container queries.
- Embed a11y into CSS with
prefers-contrast/forced-colors/reduced-motion/:focus-visible. - Optimize performance and cost with the lightness of static CSS.
Design consistency directly ties to the product's sense of trust. A product whose tokens are tidy is high not only in appearance but in maintainability and extensibility.
If you need to build a design system, or migrate to Tailwind v4 and design a theme, feel free to reach out. The case study below introduces the process of designing and implementing a UI used by multilingual, multicultural users with consistent design and operability.