Komponentenvarianten mit CVA
Verwenden Sie die Bibliothek class-variance-authority, um typisierte Varianten-APIs für React-Komponenten zu definieren und die Klassenlogik übersichtlich und vorhersehbar zu halten.
Komponentenvarianten mit CVA ist eine kostenlose Tailwind CSS Academy-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Tailwind CSS Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Tailwind CSS Academy-Kurs umfasst insgesamt 4 Lektionen.
Teile dieser Lektion wurden noch nicht übersetzt und werden auf Englisch angezeigt.
The Problem With Manual Variant Logic
Building component variant systems manually — with nested if statements and class maps — scales poorly. A button with 3 colors, 3 sizes, and a disabled state requires 18+ unique class combinations to handle correctly. The logic becomes sprawling and error-prone. Class Variance Authority (CVA) provides a structured, type-safe API for defining variant class maps, reducing variant logic to a clean declarative configuration.
Installing and Importing CVA
Install class-variance-authority with npm and import the cva function into your component file. CVA works alongside your existing cn() helper — CVA handles the variant logic, and tailwind-merge handles conflict resolution when caller classes are merged in. This combination is the foundation of libraries like shadcn/ui.
# Install
npm install class-variance-authority
# Also install companions if not present
npm install clsx tailwind-merge
// lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// In your component file
import { cva, type VariantProps } from 'class-variance-authority';Defining a Component With cva
Call cva() with two arguments: a string of base classes that always apply, and an options object with a variants map. Each key in variants is a prop name, and its value is an object mapping prop values to class strings. CVA merges the base classes with the matched variant classes when the returned function is called with prop values.
import { cva } from 'class-variance-authority';
const buttonVariants = cva(
// Base classes — always applied
'inline-flex items-center justify-center rounded-lg font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:opacity-50 disabled:pointer-events-none',
{
variants: {
variant: {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
outline: 'border border-gray-300 bg-transparent hover:bg-gray-50',
ghost: 'hover:bg-gray-100 text-gray-700'
},
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'
}
}
);Using VariantProps for TypeScript
CVA exports the VariantProps utility type that automatically infers the correct TypeScript types for your variant props from the cva definition. This means TypeScript will error if you pass an invalid variant value — like variant='purple' when only primary, secondary, outline, and ghost are defined. You get full IntelliSense autocomplete for variant values in your editor.
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva('...', { variants: { /* ... */ } });
// VariantProps extracts the type automatically
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
function Button({ variant, size, className, ...props }: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
);
}Compound Variants
CVA supports compound variants — classes that apply only when a specific combination of variant values is active. For example, you might want a special style that applies only when both variant='primary' and size='lg' are used together. This is more expressive than writing nested conditionals and keeps all variant logic in one place.
const buttonVariants = cva('base-classes', {
variants: {
variant: {
primary: 'bg-blue-600 text-white',
destructive: 'bg-red-600 text-white'
},
size: {
sm: 'h-8 px-3 text-xs',
lg: 'h-12 px-6 text-base'
}
},
compoundVariants: [
{
// Apply only when BOTH conditions are true
variant: 'primary',
size: 'lg',
class: 'shadow-lg hover:shadow-xl'
},
{
variant: 'destructive',
size: 'lg',
class: 'ring-2 ring-red-300'
}
]
});Default Variants
The defaultVariants field in the cva options specifies which variant values are used when the corresponding prop is not provided. This makes most props optional while still producing correct output. A Button component with defaultVariants: { variant: 'primary', size: 'md' } renders a medium-sized primary button when used as <Button> with no props.
const badgeVariants = cva(
'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium',
{
variants: {
variant: {
default: 'bg-gray-100 text-gray-800',
success: 'bg-green-100 text-green-800',
warning: 'bg-yellow-100 text-yellow-800',
danger: 'bg-red-100 text-red-800'
}
},
defaultVariants: {
variant: 'default' // <Badge /> without variant prop → default style
}
}
);
// All three are valid:
<Badge /> // uses default variant
<Badge variant='success' /> // green
<Badge variant='danger' /> // redCVA for Non-Button Components
CVA is not limited to buttons — use it for any component with multiple style variants. Cards can have elevated, outlined, and ghost variants. Inputs can have default and error states. Text elements can have h1 through h6 size variants. CVA's declarative API works equally well for any component where CSS classes vary based on props.
const cardVariants = cva(
'rounded-xl overflow-hidden',
{
variants: {
variant: {
elevated: 'bg-white shadow-md hover:shadow-lg transition-shadow',
outlined: 'bg-white border border-gray-200',
filled: 'bg-gray-50'
},
padding: {
none: '',
sm: 'p-4',
md: 'p-6',
lg: 'p-8'
}
},
defaultVariants: {
variant: 'elevated',
padding: 'md'
}
}
);Composing CVA Variants
Multiple CVA definitions can be composed together. A base button style can be defined with cva, and a more specific IconButton can call the base cva function and merge its result with icon-specific classes. This composition model mirrors component composition in React and keeps each variant definition focused on a single concern.
// Base shared button classes
const baseButton = cva(
'inline-flex items-center justify-center font-medium transition-colors disabled:opacity-50',
{
variants: {
variant: {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
ghost: 'hover:bg-gray-100'
}
}
}
);
// Icon button extends base
function IconButton({ icon, variant, 'aria-label': label }) {
return (
<button
className={cn(
baseButton({ variant }),
'h-10 w-10 rounded-full p-0' // icon-specific additions
)}
aria-label={label}
>
{icon}
</button>
);
}Exporting Variants for Reuse
In component libraries, it is common to export the cva function result (the variants function) alongside the component itself. This allows other components to apply the same variant classes without importing the full component. For example, a Link component might want to apply buttonVariants({ variant: 'primary' }) to render as a button visually while remaining a semantic anchor element.
// components/Button.tsx
export const buttonVariants = cva('...base...', {
variants: {
variant: {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-100 text-gray-900'
},
size: { sm: 'h-8 px-3', md: 'h-10 px-4', lg: 'h-12 px-6' }
}
});
export function Button({ variant, size, className, ...props }) {
return <button className={cn(buttonVariants({ variant, size }), className)} {...props} />;
}
// In another file: Link styled as a button
import { buttonVariants } from './Button';
<a href='/signup' className={cn(buttonVariants({ variant: 'primary', size: 'md' }))}>
Sign Up
</a>CVA With Responsive Variants
Tailwind's responsive prefixes can be included inside CVA class strings just like any other utility. This means you can define variants that include responsive behavior. For example, a layout variant might apply flex-col md:flex-row — the CVA system does not need to know about Tailwind's breakpoints; they are just class strings.
const stackVariants = cva(
'flex gap-4',
{
variants: {
direction: {
vertical: 'flex-col',
horizontal: 'flex-row',
responsive: 'flex-col sm:flex-row' // breakpoint in variant
},
align: {
start: 'items-start',
center: 'items-center',
end: 'items-end'
}
},
defaultVariants: {
direction: 'vertical',
align: 'start'
}
}
);
// Usage
<div className={stackVariants({ direction: 'responsive', align: 'center' })}>
{/* Children stack vertically on mobile, horizontally on sm+ */}
</div>Real-World CVA Component Library
A mature CVA setup defines all primitive components — Button, Badge, Input, Card, Alert — each with their own cva definitions. These components are collected into a components/ui folder (as seen in shadcn/ui), each file exporting both the React component and its variants function. This structure makes the variant system discoverable, testable, and easy to extend.
// components/ui/alert.tsx
import { cva, type VariantProps } from 'class-variance-authority';
export const alertVariants = cva(
'relative w-full rounded-lg border p-4 text-sm',
{
variants: {
variant: {
default: 'bg-white text-gray-900 border-gray-200',
destructive: 'bg-red-50 text-red-800 border-red-200',
success: 'bg-green-50 text-green-800 border-green-200'
}
},
defaultVariants: { variant: 'default' }
}
);
export function Alert({ variant, className, children, ...props }) {
return (
<div className={cn(alertVariants({ variant }), className)} role='alert' {...props}>
{children}
</div>
);
}Quick Check
Test your understanding of Tailwind CSS Mastery concepts from this lesson.
Lesson Recap
In this lesson you learned: cva() defines variant maps with base classes plus conditional class groups, VariantProps automatically derives TypeScript types from the cva definition, and compoundVariants apply classes only when specific combinations of variants are active. Next up we explore how tailwind-merge prevents class conflicts when composing components.
Häufig gestellte Fragen
Ist die Lektion „Komponentenvarianten mit CVA“ kostenlos?
Ja — der vollständige Text von „Komponentenvarianten mit CVA“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Tailwind CSS Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Tailwind CSS Academy-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Komponentenvarianten mit CVA“?
Verwenden Sie die Bibliothek class-variance-authority, um typisierte Varianten-APIs für React-Komponenten zu definieren und die Klassenlogik übersichtlich und vorhersehbar zu halten. Du übst Tailwind CSS Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Tailwind CSS Academy zu starten?
Keine Vorkenntnisse erforderlich. Tailwind CSS Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.
Wie lange dauert die Lektion „Komponentenvarianten mit CVA“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Tailwind CSS Academy-Lektion Code schreiben und ausführen?
Ja. Jede Tailwind CSS Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Tailwind in Next.js einrichten
- Bedingte Klassen in React
- Komponentenvarianten mit CVA
- Klassenkonflikte mit tailwind-merge vermeiden