Skip to content

Composing Components

This content is for Beta. Switch to the latest version for up-to-date documentation.

Shallow merge one or more cva components into a single component with the composes property. Pass a single component directly, or pass multiple components as an array:

components/card.ts
import { cva, type VariantProps } from "cva";
const box = cva({
base: "box box-border",
variants: {
margin: { 0: "m-0", 2: "m-2", 4: "m-4", 8: "m-8" },
padding: { 0: "p-0", 2: "p-2", 4: "p-4", 8: "p-8" },
},
defaultVariants: {
margin: 0,
padding: 0,
},
});
const root = cva({
base: "card rounded border-solid border-slate-300",
variants: {
shadow: {
md: "drop-shadow-md",
lg: "drop-shadow-lg",
xl: "drop-shadow-xl",
},
},
});
export const card = cva({ composes: [box, root] });
export interface CardProps extends VariantProps<typeof card> {}
card({ margin: 2, shadow: "md" });
// => "box box-border m-2 p-0 card rounded border-solid border-slate-300 drop-shadow-md"
card({ margin: 2, shadow: "md", class: "adhoc-class" });
// => "box box-border m-2 p-0 card rounded border-solid border-slate-300 drop-shadow-md adhoc-class"

If more than one composed component declares the same variant, their values combine. Each component still resolves and applies its own class, so overlapping values extend one another rather than override:

const a = cva({ base: "a", variants: { style: { primary: "a-primary" } } });
const b = cva({
base: "b",
variants: { style: { primary: "b-primary", secondary: "b-secondary" } },
});
const combined = cva({ composes: [a, b] });
combined({ style: "primary" });
// => "a a-primary b b-primary"

defaultVariants follow a last-wins merge. If multiple composed components declare a default for the same variant, the last one in the array wins. A local defaultVariants on the composing component wins over all of them. cva applies that value to every composed component, not just the one that declared it:

const a = cva({
base: "a",
variants: { style: { primary: "a-primary" } },
defaultVariants: { style: "primary" },
});
const b = cva({
base: "b",
variants: { style: { primary: "b-primary", secondary: "b-secondary" } },
defaultVariants: { style: "secondary" },
});
const combined = cva({ composes: [a, b] });
combined();
// => "a b b-secondary"