---
title: "Variants"
description: "Create variants, compound variants, and disable variants with cva@beta."
url: "https://cva.style/beta/getting-started/variants/"
---

## Creating variants

Note

Although `cva` is a tiny (1.64 KB compressed) library, it’s best to use in an environment with server-side rendering (SSR) or static-site generation (SSG): your user probably doesn’t need this JavaScript, especially for static components.

To kick things off, let’s build a “basic” `button` component, using `cva` to handle our variant’s classes

Note

Use of Tailwind CSS is optional

components/button.ts

```ts
import { cva } from "cva";


const button = cva({
  base: "rounded border font-semibold",
  // **or**
  // base: ["font-semibold", "border rounded"],
  variants: {
    intent: {
      primary: "border-transparent bg-blue-500 text-white hover:bg-blue-600",
      // **or**
      // primary: [
      //   "bg-blue-500 text-white border-transparent",
      //   "hover:bg-blue-600",
      // ],
      secondary: "border-gray-400 bg-white text-gray-800 hover:bg-gray-100",
    },
    size: {
      small: "px-2 py-1 text-sm",
      medium: "px-4 py-2 text-base",
    },
  },
  compoundVariants: [
    {
      intent: "primary",
      size: "medium",
      class: "uppercase",
      // **or** if you're a React.js user, `className` may feel more consistent:
      // className: "uppercase"
    },
  ],
  defaultVariants: {
    intent: "primary",
    size: "medium",
  },
});


button();
// => "rounded border font-semibold border-transparent bg-blue-500 text-white hover:bg-blue-600 px-4 py-2 text-base uppercase"


button({ intent: "secondary", size: "small" });
// => "rounded border font-semibold border-gray-400 bg-white text-gray-800 hover:bg-gray-100 px-2 py-1 text-sm"
```

## Default variants

`defaultVariants` supplies defaults when calling the class function. Omitting a prop, or passing `undefined`, uses its default.

Prefer framework prop defaults in UI components

For framework components (e.g. React or Vue), prefer your framework’s prop defaults so the same value is available to both styling and markup. For example, defaulting `disabled` in `defaultVariants` changes class selection but does not set the HTML button’s `disabled` attribute. Set the prop default in the wrapper and pass that value to both the class function and the element.

Use `defaultVariants` when the class function itself should supply defaults to its callers. [`getSchema`](https://cva.style/beta/getting-started/tools) reads those defaults, but cannot discover defaults declared in framework wrappers. Avoid maintaining the same default in both places.

To select no classes for a variant, declare an explicit option such as `unset: null`; see [disabling variants](#disabling-variants).

## Internal variants

A variant name prefixed with `_` is treated as internal.

Internal variants remain available to the component. You can set them via `defaultVariants` and match them in `compoundVariants`. Both [`VariantProps`](https://cva.style/beta/getting-started/typescript#extracting-variant-types) and [`getSchema`](https://cva.style/beta/api-reference#getschema) omit them.

```ts
import { cva, type VariantProps } from "cva";


const button = cva({
  base: "button",
  variants: {
    _intent: { primary: "button--primary", secondary: "button--secondary" },
    size: { small: "button--small", medium: "button--medium" },
  },
  defaultVariants: {
    _intent: "primary",
    size: "medium",
  },
});


// `_intent` is omitted from the public props.
type ButtonProps = VariantProps<typeof button>;
// => { size?: "small" | "medium" | undefined }


// The component still accepts `_intent` directly.
button({ _intent: "secondary", size: "small" });
// => "button button--secondary button--small"
```

In a React component, extend `VariantProps` for the public props, then set the internal variant yourself:

```tsx
interface Props
  extends
    React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof button> {
  active?: boolean;
}


// `_intent` never appears in `Props`, so a consumer can't set it.
function Button({ active, size, className, ...props }: Props) {
  return (
    <button
      className={button({
        size,
        _intent: active ? "primary" : "secondary",
        class: className,
      })}
      {...props}
    />
  );
}
```

A composed-only internal variant is omitted from the composer’s `VariantProps` too. You can override its inherited default directly from the composer without redeclaring the variant (see [Extending variants](https://cva.style/beta/getting-started/composing-components#extending-variants)).

```ts
const base = cva({
  variants: {
    _tone: { quiet: "tone-quiet", loud: "tone-loud" },
  },
  defaultVariants: { _tone: "quiet" },
});


const card = cva({
  composes: base,
  defaultVariants: { _tone: "loud" },
});


card();
// => "tone-loud"
```

## Compound variants

Variants that apply when multiple other variant conditions are met.

components/button.ts

```ts
import { cva } from "cva";


const button = cva({
  base: "…",
  variants: {
    intent: { primary: "…", secondary: "…" },
    size: { small: "…", medium: "…" },
  },
  compoundVariants: [
    // Applied via:
    //   `button({ intent: "primary", size: "medium" })`
    {
      intent: "primary",
      size: "medium",
      class: "…",
    },
  ],
});
```

### Targeting multiple variant conditions

components/button.ts

```ts
import { cva } from "cva";


const button = cva({
  base: "…",
  variants: {
    intent: { primary: "…", secondary: "…" },
    size: { small: "…", medium: "…" },
  },
  compoundVariants: [
    // Applied via:
    //   `button({ intent: "primary", size: "medium" })`
    //     or
    //   `button({ intent: "secondary", size: "medium" })`
    {
      intent: ["primary", "secondary"],
      size: "medium",
      class: "…",
    },
  ],
});
```

## Disabling variants

To disable a variant completely, provide an option with a value of `null`.

If you’re stuck on naming, we recommend setting an explicit `"unset"` option ([similar to the CSS keyword](https://developer.mozilla.org/en-US/docs/Web/CSS/unset)).

```ts
import { cva } from "cva";


const button = cva({
  base: "button",
  variants: {
    intent: {
      unset: null,
      primary: "button--primary",
      secondary: "button--secondary",
    },
  },
});


button({ intent: "unset" });
// => "button"
```
