Tailwind CSS Academy · Lektion

Komponentvarianter med CVA

Använd biblioteket class-variance-authority för att definiera typade variant-API:er för React-komponenter och hålla klasslogiken ren och förutsägbar.

Lektion 3 av 413 steg

Komponentvarianter med CVA är en gratis lektion i Tailwind CSS Academy på CoddyKit. Detta är lektion 3 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för Tailwind CSS Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Tailwind CSS Academy innehåller totalt 4 lektioner.

Problemet med manuell variantlogik

Att manuellt bygga system för komponentvarianter — med nästlade if-satser och klasskartor — fungerar dåligt när systemet växer. En knapp med 3 färger, 3 storlekar och ett inaktiverat tillstånd kräver över 18 unika klasskombinationer för att hanteras korrekt. Logiken blir omfattande och felbenägen. Class Variance Authority (CVA) tillhandahåller ett strukturerat, typsäkert API för att definiera klasskartor för varianter och reducerar variantlogiken till en ren deklarativ konfiguration.

Installera och importera CVA

Installera class-variance-authority med npm och importera funktionen cva till komponentfilen. CVA fungerar tillsammans med din befintliga hjälpfunktion cn() — CVA hanterar variantlogiken och tailwind-merge hanterar konfliktlösning när klasser från den anropande komponenten slås samman. Denna kombination utgör grunden för bibliotek som 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';

Definiera en komponent med cva

Anropa cva() med två argument: en sträng med basklasser som alltid tillämpas och ett optionsobjekt med en variants-karta. Varje nyckel i variants är ett propnamn och dess värde är ett objekt som mappar prop-värden till klasssträngar. CVA slår samman basklasserna med de matchande variantklasserna när den returnerade funktionen anropas med prop-värden.

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'
    }
  }
);

Använda VariantProps för TypeScript

CVA exporterar verktygstypen VariantProps, som automatiskt härleder korrekta TypeScript-typer för dina variantprops från cva-definitionen. Det innebär att TypeScript rapporterar ett fel om du skickar ett ogiltigt variantvärde — till exempel variant='purple' när endast primary, secondary, outline och ghost har definierats. Du får fullständig IntelliSense-autokomplettering för variantvärden i editorn.

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}
    />
  );
}

Sammansatta varianter

CVA har stöd för sammansatta varianter — klasser som endast tillämpas när en specifik kombination av variantvärden är aktiv. Du kanske till exempel vill ha en särskild stil som endast tillämpas när både variant='primary' och size='lg' används tillsammans. Detta är mer uttrycksfullt än att skriva nästlade villkor och håller all variantlogik samlad på ett ställe.

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'
    }
  ]
});

Standardvarianter

Fältet defaultVariants i cva-alternativen anger vilka variantvärden som ska användas när motsvarande prop inte har angetts. Det gör de flesta props valfria samtidigt som resultatet förblir korrekt. En Button-komponent med defaultVariants: { variant: 'primary', size: 'md' } renderar en primär knapp i medelstorlek när den används som <Button> utan 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' />       // red

CVA för andra komponenter än knappar

CVA är inte begränsat till knappar — använd det för alla komponenter med flera stilvarianter. Kort kan ha varianterna elevated, outlined och ghost. Inmatningsfält kan ha tillstånden default och error. Textelement kan ha storleksvarianter från h1 till h6. CVA:s deklarativa API fungerar lika bra för alla komponenter där CSS-klasser varierar beroende på 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'
    }
  }
);

Sammansätta CVA-varianter

Flera CVA-definitioner kan komponeras tillsammans. En grundläggande knappstil kan definieras med cva, och en mer specifik IconButton kan anropa den grundläggande cva-funktionen och slå samman resultatet med ikonspecifika klasser. Den här kompositionsmodellen motsvarar komponentkomposition i React och håller varje variantdefinition fokuserad på ett enda ansvarsområde.

// 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>
  );
}

Exportera varianter för återanvändning

I komponentbibliotek är det vanligt att exportera resultatet från cva-funktionen (variantfunktionen) tillsammans med själva komponenten. Då kan andra komponenter använda samma variantklasser utan att importera hela komponenten. En Link-komponent kan till exempel vilja använda buttonVariants({ variant: 'primary' }) för att återges visuellt som en knapp, samtidigt som den förblir ett semantiskt ankarelement.

// 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 med responsiva varianter

Tailwinds responsiva prefix kan inkluderas i CVA-klasssträngar precis som alla andra verktygsklasser. Det innebär att Ni kan definiera varianter som innehåller responsivt beteende. En layout-variant kan till exempel använda flex-col md:flex-row — CVA-systemet behöver inte känna till Tailwinds brytpunkter; de är bara klasssträngar.

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>

Ett CVA-komponentbibliotek i praktiken

En mogen CVA-struktur definierar alla primitiva komponenter — Button, Badge, Input, Card och Alert — var och en med sina egna cva-definitioner. Dessa komponenter samlas i mappen components/ui (som i shadcn/ui), där varje fil exporterar både React-komponenten och dess variantfunktion. Den här strukturen gör variantsystemet lätt att hitta, testa och bygga ut.

// 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>
  );
}

Snabbtest

Testa Er förståelse av begreppen i Tailwind CSS Mastery från den här lektionen.

Sammanfattning av lektionen

I den här lektionen har Ni lärt Er att cva() definierar variantmappningar med basklasser och villkorliga klassgrupper, att VariantProps automatiskt härleder TypeScript-typer från cva-definitionen och att compoundVariants använder klasser endast när specifika kombinationer av varianter är aktiva. Härnäst utforskar vi hur tailwind-merge förhindrar klasskonflikter när komponenter komponeras.

Gratis att börja

Lär dig HTML med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
30
Lektioner
120

Vanliga frågor

Är lektionen ”Komponentvarianter med CVA” gratis?

Ja – hela texten till ”Komponentvarianter med CVA” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i Tailwind CSS Academy, kan Ni uppgradera till CoddyKit PRO. Kursen i Tailwind CSS Academy innehåller totalt 4 lektioner.

Vad lär jag mig i ”Komponentvarianter med CVA”?

Använd biblioteket class-variance-authority för att definiera typade variant-API:er för React-komponenter och hålla klasslogiken ren och förutsägbar. Ni övar på Tailwind CSS Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Tailwind CSS Academy?

Du behöver inga förkunskaper. Utbildningen i Tailwind CSS Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.

Hur lång tid tar lektionen ”Komponentvarianter med CVA”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Tailwind CSS Academy-lektionen?

Ja. Varje Tailwind CSS Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Konfigurera Tailwind i Next.js
  2. Villkorliga klasser i React
  3. Komponentvarianter med CVA
  4. Undvik klasskonflikter med tailwind-merge
← Tillbaka till Tailwind CSS Academy