Skip to content

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.*?

If you’re already using a cva beta, run the cva-migrate Agent Skill to upgrade between beta versions. The rest of this page covers migrating from class-variance-authority@0.x.

TypeScript projects using cva@beta need TypeScript 6.0 or later. JavaScript usage is unaffected.

Roll your own cva via the new defineConfig API in cva/config.

Use cva/cx with cn, tailwind-merge, or your own concatenator via the cx option.

Shallow merge one or more cva components into a single component via the new composes property.

This replaces the compose function.

Extract a plain-object schema (variant names, values and defaults) from a cva component via the new cva/tools entry point’s getSchema. Use it to generate Storybook controls or any other UI that reads a component’s variants.

A variant name prefixed with _ is now treated as internal. It stays off a wrapping component’s public props because VariantProps and getSchema omit it, while the cva component still accepts it.

5. Bring your own concatenator with cva/config

Section titled “5. Bring your own concatenator with cva/config”

The cva package is a preset over the core: it wires cva and cx to clsx, like class-variance-authority. Use cva/config with a required cx option to configure clsx/lite, cn, tailwind-merge, or your own concatenator.

The compose method was deprecated in favor of the composes property inside cva, and cva@1.0 removes it: there is no compose to import. Pass a single component directly, or pass multiple components as 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] });

See Composing Components for more details and migration examples.

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!

Base styles are now applied via the named base property.

import { cva } from "class-variance-authority";
import { cva } from "cva";
const component = cva({ base: "your-base-class" });

Previously, passing null to a variant would disable it completely, to match the behavior of Stitches.js. However, this caused a great deal of confusion.

Instead, we now recommend explicitly rolling your own unset variant.

cva uses generic type parameters to infer variant types. Some users mistook these for a customization option.

If you now pass a generic type parameter, cva throws an error.

Calling a cva component is up to 1000% faster than 0.7. The runtime works out as much of the class list as it can when you create the component, then calls your concatenator once per call, instead of copying props and spreading arrays along the way:

  • 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 now 1.62 kB (brotli), up from 1.4 kB. The precomputed tables are the difference.

Thanks to @fveracoechea for inspiring this work! See the pull request for the full numbers.

6. Configuration is read when you create the component

Section titled “6. Configuration is read when you create the component”

cva used to re-read your config on every call, so mutating it afterwards quietly changed the output. It now reads base, variants, compoundVariants, defaultVariants and composes during creation. A call reads only the props you pass it, plus the cx from your defineConfig options, which stays live. That is where most of the speed above comes from.

The authoring types were reworked so TypeScript does less work to reach the same answers. Your inferred prop types, getSchema results and emitted declarations are identical. Measured with tsc --extendedDiagnostics against the previous beta:

  • A project with 200 components type-checks about 10% faster, with 26% fewer type instantiations.
  • Composed components: 16% fewer instantiations. Components with a getSchema call: 24% fewer.

See the pull request for the full numbers.