What's New?
This content is for Beta. Switch to the latest version for up-to-date documentation.
What’s changed since class-variance-authority@0.*?
Requirements
Section titled “Requirements”- Install
cva@beta. - TypeScript 6.0 or later for TypeScript projects.
- Tailwind CSS v4 for the optional
cva/tailwindcssstylesheet.
Features
Section titled “Features”1. class-variance-authority → cva
Section titled “1. class-variance-authority → cva”One of the biggest (and let’s be honest, most important) complaints about class-variance-authority was that the name was just too damn long.
Shout-out to GitHub for transferring npm ownership of cva!
2. Bring your own concatenator with cva/config
Section titled “2. Bring your own concatenator with cva/config”cva and cx still use clsx by default. For your own setup, use defineConfig from cva/config. Its required cx option accepts cn or tailwind-merge, clsx/lite for string-only classes, or your own concatenator.
shadcn
Section titled “shadcn”The shadcn CLI installs cva@beta with cn and creates cva.config.ts in your project root:
pnpm dlx shadcn@latest add joe-bell/cva/cncva/tailwindcss
Section titled “cva/tailwindcss”Import cva/tailwindcss in a Tailwind CSS v4 project and prefix component defaults with base:. Ordinary utilities override them through the CSS cascade, with no runtime class merging.
3. Performance
Section titled “3. Performance”Component calls are up to 1000% faster than class-variance-authority@0.7. cva prepares class lookups when you create the component and runs your concatenator once per call:
- Components with variants and compound variants: 300% to 550% faster per call.
- Matching a dozen compound variants: 700% to 1000% faster.
- One component called with 24 different prop shapes: 400% faster.
- Components with only a
base: 50% faster.
The cva entry is 1.64 KB (brotli), measured from the current build.
Thanks to @fveracoechea for inspiring this work! See the runtime benchmarks for the full results.
TypeScript
Section titled “TypeScript”TypeScript does less work without changing inferred props, getSchema results, or emitted declarations. The type-checking benchmarks compare against the preceding beta revision using tsc --extendedDiagnostics:
- A project with 200 components type-checks about 10% faster, with 26% fewer type instantiations.
- Composed components: 16% fewer instantiations. Components with a
getSchemacall: 24% fewer.
4. Internal variants
Section titled “4. Internal variants”Prefix a variant name with _ to make it internal. VariantProps and getSchema omit it, but you can still pass it directly to the cva component.
5. composes
Section titled “5. composes”Shallow merge one or more cva components with the composes property.
This replaces the compose function from earlier betas.
6. getSchema
Section titled “6. getSchema”getSchema from cva/tools returns a plain object of variant names, values, and defaults. Use it to generate Storybook controls or variant galleries from your component.
7. Agent Skills
Section titled “7. Agent Skills”Two Agent Skills help your coding agent work with cva:
cva-best-practicesguides component authoring.cva-migratehandles version upgrades.
8. Tailwind CSS IntelliSense
Section titled “8. Tailwind CSS IntelliSense”The Tailwind CSS IntelliSense setup now uses classFunctions instead of classRegex. It covers Visual Studio Code, Zed, Neovim, and WebStorm, and works with class-variance-authority too.
Breaking changes
Section titled “Breaking changes”1. cva now accepts a single parameter
Section titled “1. cva now accepts a single parameter”Pass base styles in the config object’s base property:
import { cva } from "class-variance-authority";import { cva } from "cva";
const component = cva("your-base-class");const component = cva({ base: "your-base-class" });The config object is required. Replace cva() with cva({}) to avoid a TypeScript error.
2. Goodbye null
Section titled “2. Goodbye null”Passing null used to disable a variant, matching Stitches.js. This caused a great deal of confusion.
TypeScript now rejects null in props, defaultVariants, and compoundVariants selectors. At runtime, a null prop applies the variant’s default class instead of disabling it. Compound variants still compare the null as-is, so it isn’t interchangeable with undefined.
Use an explicit unset variant instead:
const button = cva({ base: "button", variants: { size: { small: "button--small" } }, variants: { size: { unset: null, small: "button--small" } }, defaultVariants: { size: "small" },});
button({ size: null });button({ size: "unset" });// => "button"3. Clearer type guards
Section titled “3. Clearer type guards”cva infers variant types from your config. Passing a generic type argument now produces a TypeScript error.
4. Type imports move to the root
Section titled “4. Type imports move to the root”cva has no types entry point. Import ClassValue, ClassArray, and ClassDictionary from cva itself, alongside VariantProps.
5. Configuration is read when you create the component
Section titled “5. Configuration is read when you create the component”cva now reads base, variants, compoundVariants, defaultVariants, and composes when you create the component, which accounts for most of the runtime speed improvement. Calls read your props and the current cx from defineConfig.
6. compose → composes
Section titled “6. compose → composes”This change only affects earlier cva betas. class-variance-authority never had compose.
cva@1.0 removes the deprecated compose function. Use composes inside cva instead, passing a single component or an array:
import { cva, compose } from "cva";import { cva } from "cva";
const box = cva({ /* ... */ });const root = cva({ /* ... */ });
const card = compose(box, root);const card = cva({ composes: [box, root] });Migrating to 1.0
Section titled “Migrating to 1.0”Use the cva-migrate Agent Skill to upgrade from class-variance-authority@0.x or an earlier cva beta.