---
title: "Composing Components"
description: "Merge one or more cva components into a single component with the composes property."
url: "https://cva.style/beta/getting-started/composing-components/"
---

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

```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"
```

## Extending variants

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:

```ts
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:

```ts
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 combinedWithDefaults = cva({ composes: [a, b] });


combinedWithDefaults();
// => "a b b-secondary"
```

The composing component can set `defaultVariants` and `compoundVariants` for composed variants without redeclaring them. Both are typechecked against the merged variants, so only values a composed component declares are accepted:

```ts
const combined = cva({
  composes: [a, b],
  compoundVariants: [{ style: "primary", class: "combined-primary" }],
  defaultVariants: { style: "primary" },
});


combined();
// => "a a-primary b b-primary combined-primary"
```

Keep composition arrays immutable and typed

Pass components to `composes` as an inline array literal, or one marked `as const`. A pre-declared, mutable array variable (`const list = [a, b]`) loses the tuple inference `composes` relies on, which can silently widen or drop variant types.

`cva` reads the array once, when you create the component, so pushing another component onto it afterwards has no effect. Create the parent again with the full list instead.
