class-variance-authority

class-variance-authority (commonly called cva) is a utility library that helps create and manage CSS class variants for components. It’s especially useful in design systems and component libraries where you need consistent styling across different component states and sizes.

The library gives you a structured way to define base styles and then apply conditional styles based on properties like size, color, or state.

Below is a simple example extracted from shadcn’s button component:

import { const cva: <T>(base?: ClassValue, config?: Config<T>) => (props?: Props<T>) => stringcva } from "class-variance-authority";

const 
const buttonVariants: (props?: (ConfigVariants<{
    variant: {
        default: string;
        destructive: string;
        secondary: string;
    };
    size: {
        default: string;
        sm: string;
        lg: string;
        icon: string;
    };
}> & ClassProp) | undefined) => string
buttonVariants
=
cva<{
    variant: {
        default: string;
        destructive: string;
        secondary: string;
    };
    size: {
        default: string;
        sm: string;
        lg: string;
        icon: string;
    };
}>(base?: ClassValue, config?: {
    variants?: {
        variant: {
            default: string;
            destructive: string;
            secondary: string;
        };
        size: {
            default: string;
            sm: string;
            lg: string;
            icon: string;
        };
    } | undefined;
    defaultVariants?: ConfigVariants<{
        variant: {
            default: string;
            destructive: string;
            secondary: string;
        };
        size: {
            default: string;
            sm: string;
            lg: string;
            icon: string;
        };
    }> | undefined;
    compoundVariants?: ((ConfigVariants<{
        variant: {
            default: string;
            destructive: string;
            secondary: string;
        };
        size: {
            default: string;
            sm: string;
            lg: string;
            icon: string;
        };
    }> | ConfigVariantsMulti<...>) & ClassProp)[] | undefined;
} | undefined): (props?: (ConfigVariants<...> & ClassProp) | undefined) => string
cva
("inline-flex items-center justify-center gap-2", {
variants?: {
    variant: {
        default: string;
        destructive: string;
        secondary: string;
    };
    size: {
        default: string;
        sm: string;
        lg: string;
        icon: string;
    };
} | undefined
variants
: {
variant: {
    default: string;
    destructive: string;
    secondary: string;
}
variant
: {
default: stringdefault: "bg-primary text-primary-foreground", destructive: stringdestructive: "bg-destructive text-white", secondary: stringsecondary: "bg-secondary text-secondary-foreground", },
size: {
    default: string;
    sm: string;
    lg: string;
    icon: string;
}
size
: {
default: stringdefault: "h-9 px-4 py-2", sm: stringsm: "h-8 rounded-md gap-1.5 px-3", lg: stringlg: "h-10 rounded-md px-6", icon: stringicon: "size-9", }, },
defaultVariants?: ConfigVariants<{
    variant: {
        default: string;
        destructive: string;
        secondary: string;
    };
    size: {
        default: string;
        sm: string;
        lg: string;
        icon: string;
    };
}> | undefined
defaultVariants
: {
variant?: "default" | "destructive" | "secondary" | null | undefinedvariant: "default", size?: "default" | "sm" | "lg" | "icon" | null | undefinedsize: "default", }, });
const buttonVariants: (props?: (ConfigVariants<{
    variant: {
        default: string;
        destructive: string;
        secondary: string;
    };
    size: {
        default: string;
        sm: string;
        lg: string;
        icon: string;
    };
}> & ClassProp) | undefined) => string
buttonVariants
();
// => inline-flex items-center justify-center gap-2
const buttonVariants: (props?: (ConfigVariants<{
    variant: {
        default: string;
        destructive: string;
        secondary: string;
    };
    size: {
        default: string;
        sm: string;
        lg: string;
        icon: string;
    };
}> & ClassProp) | undefined) => string
buttonVariants
({ variant?: "default" | "destructive" | "secondary" | null | undefinedvariant: "secondary" });
// => inline-flex items-center justify-center gap-2 bg-secondary text-secondary-foreground

The cva function takes in two parameters:

  1. base: The base class name.
  2. options
    • variants: variants schema
    • compoundVariants: Variants that apply when multiple other variant conditions are met.
    • defaultVariants: Default Variants.

compoundVariants lets you apply styles when multiple conditions are met simultaneously. For example:

import { const cva: <T>(base?: ClassValue, config?: Config<T>) => (props?: Props<T>) => stringcva } from "class-variance-authority";

const 
const button: (props?: (ConfigVariants<{
    intent: {
        primary: string;
        secondary: string;
    };
    size: {
        small: string;
        medium: string;
    };
}> & ClassProp) | undefined) => string
button
=
cva<{
    intent: {
        primary: string;
        secondary: string;
    };
    size: {
        small: string;
        medium: string;
    };
}>(base?: ClassValue, config?: {
    variants?: {
        intent: {
            primary: string;
            secondary: string;
        };
        size: {
            small: string;
            medium: string;
        };
    } | undefined;
    defaultVariants?: ConfigVariants<{
        intent: {
            primary: string;
            secondary: string;
        };
        size: {
            small: string;
            medium: string;
        };
    }> | undefined;
    compoundVariants?: ((ConfigVariants<{
        intent: {
            primary: string;
            secondary: string;
        };
        size: {
            small: string;
            medium: string;
        };
    }> | ConfigVariantsMulti<{
        intent: {
            primary: string;
            secondary: string;
        };
        size: {
            small: string;
            medium: string;
        };
    }>) & ClassProp)[] | undefined;
} | undefined): (props?: (ConfigVariants<...> & ClassProp) | undefined) => string
cva
("button-base-styles", {
variants?: {
    intent: {
        primary: string;
        secondary: string;
    };
    size: {
        small: string;
        medium: string;
    };
} | undefined
variants
: {
intent: {
    primary: string;
    secondary: string;
}
intent
: { primary: stringprimary: "primary-styles", secondary: stringsecondary: "secondary-styles" },
size: {
    small: string;
    medium: string;
}
size
: { small: stringsmall: "small-styles", medium: stringmedium: "medium-styles" },
},
compoundVariants?: ((ConfigVariants<{
    intent: {
        primary: string;
        secondary: string;
    };
    size: {
        small: string;
        medium: string;
    };
}> | ConfigVariantsMulti<{
    intent: {
        primary: string;
        secondary: string;
    };
    size: {
        small: string;
        medium: string;
    };
}>) & ClassProp)[] | undefined
compoundVariants
: [
{ intent: "primary"intent: "primary", size: "medium"size: "medium", class: ClassValueclass: "bg-red-500", }, ], });

In this case, when a button has both intent: "primary" and size: "medium", it automatically gets the bg-red-500 class added.

You can even target multiple variants at once by passing an array:

import { const cva: <T>(base?: ClassValue, config?: Config<T>) => (props?: Props<T>) => stringcva } from "class-variance-authority";

const 
const button: (props?: (ConfigVariants<{
    intent: {
        primary: string;
        secondary: string;
    };
    size: {
        small: string;
        medium: string;
    };
}> & ClassProp) | undefined) => string
button
=
cva<{
    intent: {
        primary: string;
        secondary: string;
    };
    size: {
        small: string;
        medium: string;
    };
}>(base?: ClassValue, config?: {
    variants?: {
        intent: {
            primary: string;
            secondary: string;
        };
        size: {
            small: string;
            medium: string;
        };
    } | undefined;
    defaultVariants?: ConfigVariants<{
        intent: {
            primary: string;
            secondary: string;
        };
        size: {
            small: string;
            medium: string;
        };
    }> | undefined;
    compoundVariants?: ((ConfigVariants<{
        intent: {
            primary: string;
            secondary: string;
        };
        size: {
            small: string;
            medium: string;
        };
    }> | ConfigVariantsMulti<{
        intent: {
            primary: string;
            secondary: string;
        };
        size: {
            small: string;
            medium: string;
        };
    }>) & ClassProp)[] | undefined;
} | undefined): (props?: (ConfigVariants<...> & ClassProp) | undefined) => string
cva
("button-base-styles", {
variants?: {
    intent: {
        primary: string;
        secondary: string;
    };
    size: {
        small: string;
        medium: string;
    };
} | undefined
variants
: {
intent: {
    primary: string;
    secondary: string;
}
intent
: { primary: stringprimary: "primary-styles", secondary: stringsecondary: "secondary-styles" },
size: {
    small: string;
    medium: string;
}
size
: { small: stringsmall: "small-styles", medium: stringmedium: "medium-styles" },
},
compoundVariants?: ((ConfigVariants<{
    intent: {
        primary: string;
        secondary: string;
    };
    size: {
        small: string;
        medium: string;
    };
}> | ConfigVariantsMulti<{
    intent: {
        primary: string;
        secondary: string;
    };
    size: {
        small: string;
        medium: string;
    };
}>) & ClassProp)[] | undefined
compoundVariants
: [
{ intent?: "primary" | "secondary" | ("primary" | "secondary")[] | undefinedintent: ["primary", "secondary"], size?: "small" | "medium" | ("small" | "medium")[] | undefinedsize: "medium", class: ClassValueclass: "extra-medium-styles", }, ], });

Here, the extra-medium-styles class applies whenever the size is medium and the intent is either primary OR secondary.

This flexibility makes cva perfect for building maintainable component systems.

347 words

© 2023. All rights reserved.