Tailwind CSS 组件模式
Tailwind 采用工具类优先(utility-first)的思路,但在实际开发中,你往往希望把常见模式提取为可复用的组件。本技能介绍使用 Tailwind 构建可维护组件架构的策略。
核心概念
组件提取策略
使用 Tailwind 创建可复用组件有多种方式:
- 模板/组件抽象(推荐)
- CSS @apply 指令(谨慎使用)
- JavaScript/TypeScript 组件类
- Tailwind 插件
模板组件抽象
最可维护的方式是在模板层提取组件。文档以 Button.tsx 为例,展示了如何定义 variant(primary、secondary、outline)、size(sm、md、lg)等属性,并将基础类名、变体类名、尺寸类名组合后应用到 button 元素上。
最佳实践
- 使用 Class Variance Authority (CVA)
对于复杂的组件变体,使用 cva 以获得更好的类型安全。示例中通过 cva 定义基础类名,并在 variants 中声明 intent(primary、secondary、danger)、size(small、medium、large)、disabled(true:opacity-50 cursor-not-allowed),同时支持 compoundVariants 和 defaultVariants,再通过 VariantProps 推导 Button 的 props 类型。
- 使用 clsx/cn 处理条件类名
结合 clsx 与 tailwind-merge 高效合并多个类名,示例导出了 cn 函数:twMerge(clsx(inputs)),并展示了在 className 中根据 isActive、isDisabled 以及传入的 className 进行覆盖的用法。
- 创建复合组件
构建灵活的组件 API。文档以 Card.tsx 为例,定义 Card 组件并挂载 Card.Header、Card.Body、Card.Footer 子组件,各自使用 cn 组合基础样式与传入的 className,并给出使用示例。
- 谨慎使用 @apply
仅在真正需要复用的基础样式上使用 @apply。示例在 globals.css 的 @layer components 中定义 .btn、.btn-primary、.btn-secondary。文档同时提示:相比 @apply,更推荐组件抽象,以保留 Tailwind 工具类优先的优势。
- 设计系统集成
创建集中式设计系统。示例在 design-system/index.ts 中导出 colors(primary、secondary、success、danger、warning)、spacing(xs、sm、md、lg、xl)、typography(heading、body、caption),并在组件中通过模板字符串使用这些 token。
示例
表单输入组件
Input 组件基于 forwardRef 封装,支持 label、error、helperText、disabled 等属性,并通过 cn 根据 error 状态切换边框与 focus ring 颜色,同时渲染错误提示或辅助文本,最后设置 Input.displayName。
模态框组件
Modal 组件接收 isOpen、onClose、children、title、size(sm、md、lg、xl),使用 createPortal 渲染到 document.body,并在 isOpen 时锁定 body 滚动、在关闭或卸载时恢复;内部包含遮罩层(点击触发 onClose)、居中的弹窗容器、可选标题栏与关闭按钮,以及内容区。
下拉菜单组件
Dropdown 组件接收 trigger、children、align(left、right),通过 useState 管理开关状态,使用 useRef 与 useEffect 监听外部点击以关闭菜单;并挂载 Dropdown.Item 子组件,渲染为一个全宽左对齐的按钮,带 hover 背景与过渡。
常见模式
加载状态
LoadingButton 接收 isLoading 等属性,在加载时禁用按钮、降低不透明度并显示旋转的 SVG 图标,同时通过 invisible 隐藏子内容。
徽章组件
Badge 使用 cva 定义基础类名,variants 包含 variant(default、primary、success、warning、danger)与 size(sm、md、lg),defaultVariants 为 default 与 md,组件根据变体与尺寸渲染 span。
反模式
- 不要滥用 @apply:文档以“重新造一个 Bootstrap”为例,列出 .btn、.btn-primary、.btn-secondary、.btn-sm、.btn-lg 等写法为反例,建议改用组件抽象。
- 不要创建过于通用的组件:如 Box padding="4" margin="2" bg="blue" 会失去 Tailwind 的优势,应使用语义化组件,如 Card className="p-4 m-2 bg-blue-500"。
- 不要忽略 prop 覆盖:组件应接受 className 属性,并通过 cn 将基础类名与传入的 className 合并,以便外部覆盖样式。
相关技能
- tailwind-utility-classes:有效使用 Tailwind 的工具类
- tailwind-configuration:自定义 Tailwind 配置与主题
- tailwind-responsive-design:构建响应式组件模式