1. What is CVA?Understand in one sentence
CVA = Use "configuration" to describe class combination rules, and use "parameters" to generate className
It solves only one problem:
"Conditional Combination Explosion" of Component Styles
2. What would happen without a CVA?(pain point)
❌ Traditional writing method (unmaintainable)
tsx
<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"}
`}
>Question:
- Conditional dispersion
- No type hints
- Add a variant → rewrite a bunch of if
3. CVA’s core API
ts
import { cva } from "class-variance-authority"minimal model
ts
const buttonVariants = cva(
"base-class",
{
variants: {
variant: {
primary: "...",
outline: "...",
},
size: {
sm: "...",
lg: "...",
},
},
}
)Use:
ts
buttonVariants({ variant: "primary", size: "lg" })4. Complete Button example (key points)
ts
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",
},
}
)Use:
tsx
<Button variant="outline" size="lg" />5. Variants / defaultVariants essence
What are variants?
ts
variants: {
size: {
sm: "h-9",
lg: "h-11",
}
}👉 key is parameter, value is class
What are defaultVariants?
ts
defaultVariants: {
size: "md"
}👉 Default value when no parameters are passed
6. Compound conditions: compoundVariants (very important)
Append class when multiple variants satisfy at the same time
ts
compoundVariants: [
{
variant: "outline",
size: "lg",
class: "uppercase"
}
]Equivalent to:
ts
if (variant === "outline" && size === "lg") {
class += " uppercase"
}7. Boolean variant (commonly used)
ts
variants: {
disabled: {
true: "opacity-50 pointer-events-none",
false: ""
}
}Use:
tsx
<Button disabled />8. CVA + TypeScript (core advantages)
Automatically derive Props types
ts
import { VariantProps } from "class-variance-authority"
type ButtonVariants = VariantProps<typeof buttonVariants>
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
ButtonVariants {}Effect:
tsx
<Button variant="xxx" /> ❌ TS 报错
<Button size="lg" /> ✅9. className merge (required test)
tsx
<button
className={cn(buttonVariants({ variant, size }), className)}
/>Users can:
tsx
<Button className="w-full" />10. Design ideas of CVA (very important)
1️⃣ Configure the driver, not the logical driver
❌ if / else ✅ declarative config
2️⃣ Style = part of the design system
ts
variant="destructive"Not:
ts
bg-red-5003️⃣ The style is "Finite State Machine"
- variant
- size
- state
11. When should CVA be used?
✅ Used in:
- Button
- Badge
- Alert
- Input
- Tabs Trigger
❌ Not required in:
- Page level layout
- Strong business coupling components
- disposable components
12. Practical combat: write a Badge yourself (step by step)
ts
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",
},
}
)tsx
function Badge({ variant }: VariantProps<typeof badgeVariants>) {
return <span className={badgeVariants({ variant })} />
}13. Common Misunderstandings
- ❌ Write dozens of utilities in variant
- ❌ Treat business status (loading) as variant
- ❌ Add business judgment to UI components
14. Are you considered “good at CVA”?
You can:
- Write out CVA configuration without reading the documentation
- Correctly split variant / size / state
- Use compoundVariants
- Constrain component APIs with TS types