code-architecture-tailwind-v4-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTailwind CSS v4: Best Practices
Tailwind CSS v4:最佳实践
Core Principle
核心原则
Use utilities directly in markup as the primary approach. Abstract with CVA/tailwind-variants only when you have 3+ variants.
Tailwind v4's CSS-first configuration eliminates entirely. All configuration happens in CSS via directive.
tailwind.config.js@theme主要方法是在标记中直接使用工具类。仅当组件有3个及以上变体时,才使用CVA或tailwind-variants进行抽象封装。
Tailwind v4的CSS优先配置完全移除了文件,所有配置都通过CSS中的指令完成。
tailwind.config.js@themeThe CSS-First Setup
CSS优先配置方案
css
@import "tailwindcss";
@theme {
--color-brand-primary: oklch(0.65 0.24 354.31);
--color-brand-secondary: oklch(0.72 0.11 178);
--font-sans: "Inter", sans-serif;
--radius-button: 0.5rem;
}Key v4 changes:
- Single replaces three
@import "tailwindcss"directives@tailwind - generates color utilities AND exposes as CSS variables
--color-* - Automatic template discovery (respects )
.gitignore - Oxide engine: 3.5x faster full builds, 8x faster incremental
css
@import "tailwindcss";
@theme {
--color-brand-primary: oklch(0.65 0.24 354.31);
--color-brand-secondary: oklch(0.72 0.11 178);
--font-sans: "Inter", sans-serif;
--radius-button: 0.5rem;
}v4的关键变化:
- 单个语句替代了原有的三个
@import "tailwindcss"指令@tailwind - 变量既生成颜色工具类,又作为CSS变量暴露使用
--color-* - 自动识别模板文件(会遵循规则)
.gitignore - Oxide引擎:完整构建速度提升3.5倍,增量构建速度提升8倍
When to Abstract
何时进行抽象封装
✅ Use Pure Utilities When
✅ 直接使用纯工具类的场景
- Component has 1-2 variants
- Prototyping or simple components
- Bundle size is critical (0KB overhead)
tsx
// ✅ Simple button - no abstraction needed
<button className="
inline-flex items-center justify-center gap-2
px-4 py-2
bg-blue-500 hover:bg-blue-600 active:bg-blue-700
text-white text-sm font-medium
rounded-md transition-colors
focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500
disabled:opacity-50 disabled:pointer-events-none
">
Save Changes
</button>- 组件只有1-2个变体
- 原型开发或简单组件
- 对包体积要求严格(无额外体积开销)
tsx
// ✅ 简单按钮 - 无需抽象
<button className="
inline-flex items-center justify-center gap-2
px-4 py-2
bg-blue-500 hover:bg-blue-600 active:bg-blue-700
text-white text-sm font-medium
rounded-md transition-colors
focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500
disabled:opacity-50 disabled:pointer-events-none
">
保存更改
</button>✅ Use CVA When
✅ 使用CVA的场景
- 3+ variants needed
- Type safety required
- Building component library
- ~1KB bundle cost acceptable
typescript
import { cva, type VariantProps } from 'class-variance-authority';
const buttonVariants = cva(
// Base classes
"inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
primary: "bg-blue-500 text-white hover:bg-blue-600",
secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300",
outline: "border-2 border-blue-500 text-blue-500 hover:bg-blue-50",
ghost: "text-blue-500 hover:bg-blue-50"
},
size: {
sm: "h-8 px-3 text-xs",
md: "h-10 px-4 text-sm",
lg: "h-12 px-6 text-base"
}
},
defaultVariants: {
variant: "primary",
size: "md"
}
}
);
export type ButtonProps = VariantProps<typeof buttonVariants>;- 需要3个及以上变体
- 要求类型安全
- 构建组件库
- 可接受约1KB的包体积开销
typescript
import { cva, type VariantProps } from 'class-variance-authority';
const buttonVariants = cva(
// 基础类
"inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
primary: "bg-blue-500 text-white hover:bg-blue-600",
secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300",
outline: "border-2 border-blue-500 text-blue-500 hover:bg-blue-50",
ghost: "text-blue-500 hover:bg-blue-50"
},
size: {
sm: "h-8 px-3 text-xs",
md: "h-10 px-4 text-sm",
lg: "h-12 px-6 text-base"
}
},
defaultVariants: {
variant: "primary",
size: "md"
}
}
);
export type ButtonProps = VariantProps<typeof buttonVariants>;✅ Use Tailwind-Variants When
✅ 使用Tailwind-Variants的场景
- Responsive variants needed
- Multi-part/slot components (cards, accordions)
- Component composition via
extend - ~4KB bundle cost acceptable
typescript
import { tv, type VariantProps } from 'tailwind-variants';
const card = tv({
slots: {
base: 'rounded-lg border bg-card shadow-sm',
header: 'flex flex-col space-y-1.5 p-6',
title: 'text-2xl font-semibold',
content: 'p-6 pt-0',
footer: 'flex items-center p-6 pt-0'
},
variants: {
variant: {
elevated: { base: 'shadow-xl' },
flat: { base: 'shadow-none border' }
}
}
});
const { base, header, title, content, footer } = card({ variant: 'elevated' });- 需要响应式变体
- 多部分/插槽组件(如卡片、折叠面板)
- 通过实现组件组合
extend - 可接受约4KB的包体积开销
typescript
import { tv, type VariantProps } from 'tailwind-variants';
const card = tv({
slots: {
base: 'rounded-lg border bg-card shadow-sm',
header: 'flex flex-col space-y-1.5 p-6',
title: 'text-2xl font-semibold',
content: 'p-6 pt-0',
footer: 'flex items-center p-6 pt-0'
},
variants: {
variant: {
elevated: { base: 'shadow-xl' },
flat: { base: 'shadow-none border' }
}
}
});
const { base, header, title, content, footer } = card({ variant: 'elevated' });❌ Don't Use @apply
❌ 不要使用@apply
The Tailwind team discourages except in narrow circumstances. Use component abstractions instead.
@applycss
/* ❌ Avoid - hides styling decisions, breaks variant support */
.btn-primary {
@apply bg-blue-500 text-white px-4 py-2 rounded;
}
/* ✅ Use @utility for custom utilities if absolutely needed */
@utility btn-base {
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
}Tailwind团队不建议使用,仅在极少数情况下可以考虑。应使用组件抽象方案替代。
@applycss
/* ❌ 避免使用 - 隐藏样式决策,破坏变体支持 */
.btn-primary {
@apply bg-blue-500 text-white px-4 py-2 rounded;
}
/* ✅ 如果确实需要,使用@utility定义自定义工具类 */
@utility btn-base {
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
}Decision Matrix
决策矩阵
| Approach | Bundle | Type Safe | Use Case |
|---|---|---|---|
| Pure Tailwind | 0KB | ❌ | Simple, 1-2 variants, prototyping |
| CVA | ~1KB | ✅ | Component libraries, most projects |
| Tailwind-variants | ~4KB | ✅ | Complex design systems, slots |
| 方案 | 包体积 | 类型安全 | 使用场景 |
|---|---|---|---|
| 纯Tailwind | 0KB | ❌ | 简单组件、1-2个变体、原型开发 |
| CVA | ~1KB | ✅ | 组件库、大多数项目 |
| Tailwind-variants | ~4KB | ✅ | 复杂设计系统、插槽组件 |
State Management with Data Attributes
使用数据属性管理状态
V4 supports native data attributes for clean state management:
tsx
export function Button({ isLoading, isDisabled, children }: ButtonProps) {
return (
<button
data-loading={isLoading ?? ""}
data-disabled={isDisabled ?? ""}
className="
bg-blue-500 text-white px-4 py-2 rounded
hover:bg-blue-600
data-loading:opacity-50 data-loading:cursor-wait
data-disabled:opacity-50 data-disabled:pointer-events-none
"
>
{isLoading && <Spinner className="mr-2" />}
{children}
</button>
);
}Custom variants via :
@custom-variantcss
@custom-variant selected-not-disabled (&[data-selected]:not([data-disabled]));V4支持原生数据属性,可用于简洁的状态管理:
tsx
export function Button({ isLoading, isDisabled, children }: ButtonProps) {
return (
<button
data-loading={isLoading ?? ""}
data-disabled={isDisabled ?? ""}
className="
bg-blue-500 text-white px-4 py-2 rounded
hover:bg-blue-600
data-loading:opacity-50 data-loading:cursor-wait
data-disabled:opacity-50 data-disabled:pointer-events-none
"
>
{isLoading && <Spinner className="mr-2" />}
{children}
</button>
);
}通过定义自定义变体:
@custom-variantcss
@custom-variant selected-not-disabled (&[data-selected]:not([data-disabled]));Modern React Pattern (shadcn/ui style)
现代React模式(shadcn/ui风格)
tsx
import { tv, type VariantProps } from 'tailwind-variants';
const buttonStyles = tv({
base: "inline-flex items-center justify-center rounded-md font-medium transition-colors",
variants: {
variant: {
primary: "bg-blue-500 text-white hover:bg-blue-600",
secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300"
},
size: {
sm: "h-8 px-3 text-xs",
md: "h-10 px-4 text-sm"
}
}
});
type ButtonProps = React.ComponentProps<"button"> &
VariantProps<typeof buttonStyles>;
export function Button({ variant, size, className, ...props }: ButtonProps) {
return (
<button
data-slot="button"
className={cn(buttonStyles({ variant, size }), className)}
{...props}
/>
);
}tsx
import { tv, type VariantProps } from 'tailwind-variants';
const buttonStyles = tv({
base: "inline-flex items-center justify-center rounded-md font-medium transition-colors",
variants: {
variant: {
primary: "bg-blue-500 text-white hover:bg-blue-600",
secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300"
},
size: {
sm: "h-8 px-3 text-xs",
md: "h-10 px-4 text-sm"
}
}
});
type ButtonProps = React.ComponentProps<"button"> &
VariantProps<typeof buttonStyles>;
export function Button({ variant, size, className, ...props }: ButtonProps) {
return (
<button
data-slot="button"
className={cn(buttonStyles({ variant, size }), className)}
{...props}
/>
);
}Accessibility Checklist
无障碍访问检查清单
tsx
<button
type="button"
disabled={disabled || loading}
aria-disabled={disabled || loading}
aria-busy={loading}
aria-label={ariaLabel}
className={buttonStyles({ variant, size })}
>
{loading && <Spinner aria-hidden="true" />}
{leftIcon && <span data-slot="icon">{leftIcon}</span>}
<span data-slot="label">{children}</span>
</button>tsx
<button
type="button"
disabled={disabled || loading}
aria-disabled={disabled || loading}
aria-busy={loading}
aria-label={ariaLabel}
className={buttonStyles({ variant, size })}
>
{loading && <Spinner aria-hidden="true" />}
{leftIcon && <span data-slot="icon">{leftIcon}</span>}
<span data-slot="label">{children}</span>
</button>Breaking Changes from v3
与v3相比的破坏性变更
| v3 | v4 |
|---|---|
| |
| |
| |
| |
| |
| |
Automated migration:
npx @tailwindcss/upgrade| v3 | v4 |
|---|---|
| |
| |
| |
| |
| |
| |
自动迁移工具:
npx @tailwindcss/upgradeQuick Reference
快速参考
DO
推荐做法
- Use utilities directly for simple components
- Wait for 3+ variants before using CVA/tailwind-variants
- Use data attributes for state management
- Follow shadcn/ui patterns for React components
- Use @theme for design tokens (generates utilities + CSS vars)
- 简单组件直接使用工具类
- 等到3个及以上变体时再使用CVA/tailwind-variants
- 使用数据属性管理状态
- 遵循shadcn/ui模式开发React组件
- 使用**@theme**定义设计令牌(同时生成工具类和CSS变量)
DON'T
不推荐做法
- Use for component styles
@apply - Abstract prematurely (same rule as code abstractions)
- Mix approaches inconsistently within a project
- Forget accessibility attributes on interactive elements
- 使用编写组件样式
@apply - 过早进行抽象封装(与代码抽象原则一致)
- 项目中混合使用不同方案,缺乏一致性
- 交互式元素遗漏无障碍访问属性
Recommended Stack (2025)
推荐技术栈(2025)
- React: Next.js 15 + shadcn/ui + CVA + Tailwind v4
- Vue: Vue 3 + shadcn/vue + Tailwind v4
- Bundle: CVA (~1KB) + clsx (~0.2KB) + tailwind-merge (~7KB) ≈ 8KB total
- React:Next.js 15 + shadcn/ui + CVA + Tailwind v4
- Vue:Vue 3 + shadcn/vue + Tailwind v4
- 包体积:CVA(约1KB)+ clsx(约0.2KB)+ tailwind-merge(约7KB)≈ 总计8KB