Glittering's blog Glittering's blog
Home
  • 学习手册

    • 《JavaScript教程》
    • 《TypeScript教程》
    • 《Git》
    • 《Vite》
    • 《Vue3》
    • 《React18》
    • 《CSS》
    • 《Tailwind CSS》
    • 《ES6 教程》
    • 《TypeScript 从零实现 axios》
  • 技术文档
  • 算法
  • 工作总结
  • 实用技巧
  • collect
About
  • Classification
  • Label
GitHub (opens new window)

Glitz Ma

前端开发工程师
Home
  • 学习手册

    • 《JavaScript教程》
    • 《TypeScript教程》
    • 《Git》
    • 《Vite》
    • 《Vue3》
    • 《React18》
    • 《CSS》
    • 《Tailwind CSS》
    • 《ES6 教程》
    • 《TypeScript 从零实现 axios》
  • 技术文档
  • 算法
  • 工作总结
  • 实用技巧
  • collect
About
  • Classification
  • Label
GitHub (opens new window)
  • CSS

    • CSS教程和技巧收藏
    • css块元素和行内元素
    • 盒子模型
    • BFC和IFC
    • 字体font-weight相关知识
    • CSS-function汇总
    • CSS3之has函数的使用
    • CSS3之transition过渡
    • CSS3之animation动画
    • css动画性能优化
    • flex布局语法
    • flex布局案例
    • Grid布局语法
    • flex布局和grid布局的区别
    • 「布局技巧」图片未加载前自动撑开元素高度
    • shadcn/ui学习笔记
    • shadcn/ui CVA 是什么
      • 一、CVA 是什么?一句话理解
      • 二、没有 CVA 会发生什么?(痛点)
        • ❌ 传统写法(不可维护)
      • 三、CVA 的核心 API
        • 最小模型
      • 四、完整 Button 示例(重点)
      • 五、variants / defaultVariants 本质
        • variants 是什么?
        • defaultVariants 是什么?
      • 六、复合条件:compoundVariants(很重要)
      • 七、布尔型 variant(常用)
      • 八、CVA + TypeScript(核心优势)
        • 自动推导 Props 类型
      • 九、className 合并(必考)
      • 十、CVA 的设计思想(非常重要)
        • 1️⃣ 配置驱动,而不是逻辑驱动
        • 2️⃣ 样式 = 设计系统的一部分
        • 3️⃣ 样式是“有限状态机”
      • 十一、什么时候该用 CVA?
      • 十二、实战:自己写一个 Badge(一步步)
      • 十三、常见误区
      • 十四、你算“会 CVA”了吗?
    • shadcn/ui radix ui 讲解
    • h5适配方案
    • 前端1px的几种实现方案
    • 文字在一行或多行时超出显示省略号
    • 水平垂直居中的几种方式-案例
    • 如何根据系统主题自动响应CSS深色模式
    • 工作中遇到的css问题记录
    • 今天总结一下用到的css吧
  • 页面
  • CSS
mamingjuan
2025-04-18
目录

shadcn/ui CVA 是什么

# 一、CVA 是什么?一句话理解

CVA = 用「配置」描述 class 组合规则,用「参数」生成 className

它解决的问题只有一个:

组件样式的“条件组合爆炸”


# 二、没有 CVA 会发生什么?(痛点)

# ❌ 传统写法(不可维护)

<button
  className={`
    px-4 py-2 rounded
    ${variant === "primary" && "bg-blue-600 text-white"}
    ${variant === "outline" && "border"}
    ${size === "lg" && "h-12 text-lg"}
    ${disabled && "opacity-50"}
  `}
>
1
2
3
4
5
6
7
8
9

问题:

  • 条件分散
  • 无类型提示
  • 新增一个 variant → 重写一堆 if

# 三、CVA 的核心 API

import { cva } from "class-variance-authority"
1

# 最小模型

const buttonVariants = cva(
  "base-class",
  {
    variants: {
      variant: {
        primary: "...",
        outline: "...",
      },
      size: {
        sm: "...",
        lg: "...",
      },
    },
  }
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

使用:

buttonVariants({ variant: "primary", size: "lg" })
1

# 四、完整 Button 示例(重点)

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md font-medium",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground",
        outline: "border border-input",
        ghost: "hover:bg-accent",
      },
      size: {
        sm: "h-9 px-3",
        md: "h-10 px-4",
        lg: "h-11 px-8",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "md",
    },
  }
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

使用:

<Button variant="outline" size="lg" />
1

# 五、variants / defaultVariants 本质

# variants 是什么?

variants: {
  size: {
    sm: "h-9",
    lg: "h-11",
  }
}
1
2
3
4
5
6

👉 key 是参数,value 是 class

# defaultVariants 是什么?

defaultVariants: {
  size: "md"
}
1
2
3

👉 不传参数时的默认值


# 六、复合条件:compoundVariants(很重要)

当 多个 variant 同时满足 时追加 class

compoundVariants: [
  {
    variant: "outline",
    size: "lg",
    class: "uppercase"
  }
]
1
2
3
4
5
6
7

相当于:

if (variant === "outline" && size === "lg") {
  class += " uppercase"
}
1
2
3

# 七、布尔型 variant(常用)

variants: {
  disabled: {
    true: "opacity-50 pointer-events-none",
    false: ""
  }
}
1
2
3
4
5
6

使用:

<Button disabled />
1

# 八、CVA + TypeScript(核心优势)

# 自动推导 Props 类型

import { VariantProps } from "class-variance-authority"

type ButtonVariants = VariantProps<typeof buttonVariants>

interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    ButtonVariants {}
1
2
3
4
5
6
7

效果:

<Button variant="xxx" /> ❌ TS 报错
<Button size="lg" /> ✅
1
2

# 九、className 合并(必考)

<button
  className={cn(buttonVariants({ variant, size }), className)}
/>
1
2
3

用户可以:

<Button className="w-full" />
1

# 十、CVA 的设计思想(非常重要)

# 1️⃣ 配置驱动,而不是逻辑驱动

❌ if / else ✅ declarative config


# 2️⃣ 样式 = 设计系统的一部分

variant="destructive"
1

不是:

bg-red-500
1

# 3️⃣ 样式是“有限状态机”

  • variant
  • size
  • state

# 十一、什么时候该用 CVA?

✅ 用在:

  • Button
  • Badge
  • Alert
  • Input
  • Tabs Trigger

❌ 不用在:

  • 页面级布局
  • 强业务耦合组件
  • 一次性组件

# 十二、实战:自己写一个 Badge(一步步)

const badgeVariants = cva(
  "inline-flex items-center rounded-full px-2 py-1 text-xs",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground",
        success: "bg-green-500 text-white",
        warning: "bg-yellow-500 text-black",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Badge({ variant }: VariantProps<typeof badgeVariants>) {
  return <span className={badgeVariants({ variant })} />
}
1
2
3

# 十三、常见误区

  • ❌ variant 里写几十个 utility
  • ❌ 把业务状态(loading)当 variant
  • ❌ 在 UI 组件里加业务判断

# 十四、你算“会 CVA”了吗?

你能:

  • 不看文档写出 CVA 配置
  • 正确拆 variant / size / state
  • 用 compoundVariants
  • 用 TS 类型约束组件 API
上次更新: 2026/01/07, 09:20:46
shadcn/ui学习笔记
shadcn/ui radix ui 讲解

← shadcn/ui学习笔记 shadcn/ui radix ui 讲解→

Copyright © 2015-2026 Glitz Ma
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式