Migrating
The cva package (v1+) is the successor to class-variance-authority (v0.x). It ships new features — compose and defineConfig — and changes the cva() call signature from positional arguments to a config object.
Package rename
The class-variance-authority package is no longer receiving new features. Migrate to cva to access compose, defineConfig, and future releases.
Uninstall the legacy package
npm uninstall class-variance-authority
Update imports
Replace all imports from class-variance-authority with cva.-import { cva, cx, type VariantProps } from "class-variance-authority";
+import { cva, cx, type VariantProps } from "cva";
Update cva() call signatures
The legacy package accepted two positional arguments: cva(base, options). The new package accepts a single config object: cva({ base, variants, compoundVariants, defaultVariants }).This is a breaking change. Every cva() call must be updated.
Before (class-variance-authority v0.x)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: {
sm: ["text-sm", "py-1", "px-2"],
md: ["text-base", "py-2", "px-4"],
},
},
compoundVariants: [
{ intent: "primary", size: "md", class: "uppercase" },
],
defaultVariants: {
intent: "primary",
size: "md",
},
});
After (cva v1+)const button = 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"],
},
size: {
sm: ["text-sm", "py-1", "px-2"],
md: ["text-base", "py-2", "px-4"],
},
},
compoundVariants: [
{ intent: "primary", size: "md", class: "uppercase" },
],
defaultVariants: {
intent: "primary",
size: "md",
},
});
The diff in one place:-const button = cva(["font-semibold", "border", "rounded"], {
- variants: { … },
- compoundVariants: [ … ],
- defaultVariants: { … },
-});
+const button = cva({
+ base: ["font-semibold", "border", "rounded"],
+ variants: { … },
+ compoundVariants: [ … ],
+ defaultVariants: { … },
+});
New features in cva v1+
compose
compose is a new helper that merges multiple CVA components into one. It is not available in class-variance-authority.
import { cva, compose } from "cva";
const box = cva({
base: "box box-border",
variants: {
margin: { 0: "m-0", 4: "m-4" },
padding: { 0: "p-0", 4: "p-4" },
},
defaultVariants: { margin: 0, padding: 0 },
});
const card = cva({
base: "card border-solid border-slate-300 rounded",
variants: {
shadow: { md: "drop-shadow-md", lg: "drop-shadow-lg" },
},
});
const cardBox = compose(box, card);
cardBox({ margin: 4, padding: 4, shadow: "md" });
// => "box box-border m-4 p-4 card border-solid border-slate-300 rounded drop-shadow-md"
See the compose API reference for full documentation.
defineConfig
defineConfig lets you configure a custom onComplete hook that runs after every class concatenation — the primary use case is integrating tailwind-merge globally:
import { defineConfig } from "cva";
import { twMerge } from "tailwind-merge";
export const { cva, cx, compose } = defineConfig({
hooks: {
onComplete: (className) => twMerge(className),
},
});
See the defineConfig API reference for full documentation.