Documentation Index
Fetch the complete documentation index at: https://mintlify.com/joe-bell/cva/llms.txt
Use this file to discover all available pages before exploring further.
TypeScript
CVA is written in TypeScript and ships full type declarations. The VariantProps utility type is the primary tool for connecting your cva definitions to your component interfaces.
VariantProps<typeof component> infers the variant prop types directly from your cva definition. This means your types and your variant config stay in sync automatically — no manual duplication:
import { cva, type VariantProps } from "class-variance-authority";
const button = cva(["font-semibold", "border", "rounded"], {
variants: {
intent: {
primary: ["bg-blue-500", "text-white", "border-transparent"],
secondary: ["bg-white", "text-gray-800", "border-gray-400"],
},
size: {
small: ["text-sm", "py-1", "px-2"],
medium: ["text-base", "py-2", "px-4"],
},
},
});
export type ButtonProps = VariantProps<typeof button>;
// => { intent?: "primary" | "secondary"; size?: "small" | "medium" }
Using VariantProps in a component
Extend your component’s props interface with VariantProps and forward the variant props into the cva call:import type { VariantProps } from "class-variance-authority";
import { cva } from "class-variance-authority";
const button = cva(["font-semibold", "border", "rounded"], {
variants: {
intent: {
primary: ["bg-blue-500", "text-white", "border-transparent"],
secondary: ["bg-white", "text-gray-800", "border-gray-400"],
},
size: {
small: ["text-sm", "py-1", "px-2"],
medium: ["text-base", "py-2", "px-4"],
},
},
defaultVariants: {
intent: "primary",
size: "medium",
},
});
interface ButtonProps
extends VariantProps<typeof button>,
React.ButtonHTMLAttributes<HTMLButtonElement> {}
export function Button({ intent, size, className, ...props }: ButtonProps) {
return (
<button
className={button({ intent, size, className })}
{...props}
/>
);
}
VariantProps works the same way in any TypeScript-based framework. Extract the type from the cva definition and use it in your component’s props:import type { VariantProps } from "class-variance-authority";
import { cva } from "class-variance-authority";
export const button = cva(["font-semibold", "border", "rounded"], {
variants: {
intent: {
primary: ["bg-blue-500", "text-white", "border-transparent"],
secondary: ["bg-white", "text-gray-800", "border-gray-400"],
},
size: {
small: ["text-sm", "py-1", "px-2"],
medium: ["text-base", "py-2", "px-4"],
},
},
});
export type ButtonVariantProps = VariantProps<typeof button>;
Required variants
All variant props are optional by default (they can fall back to defaultVariants). To make a variant required, use TypeScript utility types:
import { cva, type VariantProps } from "class-variance-authority";
export type ButtonVariantProps = VariantProps<typeof buttonVariants>;
export const buttonVariants = cva("…", {
variants: {
optional: { a: "…", b: "…" },
required: { a: "…", b: "…" },
},
});
/**
* Button — "required" variant must always be provided
*/
export interface ButtonProps
extends Omit<ButtonVariantProps, "required">,
Required<Pick<ButtonVariantProps, "required">> {}
export const button = (props: ButtonProps) => buttonVariants(props);
// ❌ TypeScript Error:
// Argument of type '{}' is not assignable to parameter of type 'ButtonProps'.
// Property 'required' is missing in type '{}' but required in type 'ButtonProps'.
button({});
// ✅
button({ required: "a" });
The class and className props
CVA accepts either class or className for one-off class overrides. The two props are mutually exclusive — the type system prevents you from passing both at once:
// cva's CVAClassProp type (from source)
type CVAClassProp =
| { class?: ClassValue; className?: never }
| { class?: never; className?: ClassValue };
This means VariantProps intentionally omits both class and className from the extracted type. Pass them directly to the component call instead:
button({ intent: "primary", class: "m-4" });
button({ intent: "primary", className: "m-4" });
The ClassValue type
ClassValue is re-exported from CVA and mirrors the type accepted by clsx. Use it when you want a component to accept arbitrary class inputs:
import type { ClassValue } from "class-variance-authority";
// or from the cva package:
import type { ClassValue } from "cva";
// ClassValue accepts strings, arrays, objects, booleans, null, and undefined
type ClassValue =
| string
| number
| bigint
| boolean
| null
| undefined
| ClassDictionary
| ClassArray;
A common pattern is to accept ClassValue for an extra classes prop and merge it with the CVA output:
import { cva, cx, type ClassValue, type VariantProps } from "cva";
const buttonVariants = cva({
base: ["font-semibold", "border", "rounded"],
variants: {
intent: {
primary: ["bg-blue-500", "text-white", "border-transparent"],
secondary: ["bg-white", "text-gray-800", "border-gray-400"],
},
},
defaultVariants: { intent: "primary" },
});
interface ButtonProps extends VariantProps<typeof buttonVariants> {
extraClasses?: ClassValue;
}
export function button({ intent, extraClasses }: ButtonProps) {
return cx(buttonVariants({ intent }), extraClasses);
}