>
);
```
* Create a bespoke variant that changes based on the breakpoint.
*e.g. `button({ intent: "primaryUntilMd" })`*
This is something I’ve been thinking about since the project’s inception, and I’ve gone back and forth many times on the idea of building it. It’s a large undertaking and brings all the complexity of supporting many different build tools and frameworks.
In my experience, “responsive variants” are typically rare, and hiding/showing different elements is usually good enough to get by.
To be frank, I’m probably not going to build/maintain a solution unless someone periodically gives me a thick wad of cash to do so, and even then I’d probably rather spend my free time living my life.
# Composing Components
> Merge one or more cva components into a single component with the composes property.
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 {}
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
[Section titled “Extending variants”](#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"
```
Caution
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.
# Compound Components
> Build compound component sets, such as Accordion.Item, with cva and CSS.
For larger, more complex components, you may end up wanting to create a set of composable components that work together: “Compound Components”
e.g. `` instead of ``
```tsx
import * as Accordion from "./Accordion";
function Example() {
return (
Section 1Content 1
);
}
```
`cva` encourages you to build these compound components with CSS: leverage the cascade, custom properties, `:has()` selectors, and more…
## Examples
[Section titled “Examples”](#examples)
* [React with Tailwind CSS (Compound Components)](/beta/examples/react/tailwindcss#compound-components)
# Extending Components
> Pass extra classes to a cva component with the class or className prop.
All `cva` components provide an optional `class` **or** `className` prop, which can be used to pass additional classes to the component.
components/button.ts
```ts
import { cva } from "cva";
const button = cva({ base: "font-semibold" });
button({ class: "m-4" });
// => "font-semibold m-4"
button({ className: "m-4" });
// => "font-semibold m-4"
```
# Installation
> Install cva@beta, configure Tailwind CSS IntelliSense, and merge conflicting utility classes.
* pnpm
```sh
pnpm i cva@beta
```
* npm
```sh
npm i cva@beta
```
* yarn
```sh
yarn add cva@beta
```
* bun
```sh
bun add cva@beta
```
* deno
```sh
deno add cva@beta
```
## Tailwind CSS
[Section titled “Tailwind CSS”](#tailwind-css)
If you’re a Tailwind user, here are some additional (optional) steps to get the most out of `cva`:
### IntelliSense
[Section titled “IntelliSense”](#intellisense)
You can enable autocompletion inside `cva` using the steps below:
* Visual Studio Code
1. [Install the “Tailwind CSS IntelliSense” Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)
2. Add the following to your [`.vscode/settings.json`](https://code.visualstudio.com/docs/getstarted/settings):
.vscode/settings.json
```json
{
"tailwindCSS.classFunctions": ["cva", "cx"],
}
```
* Zed
Add the following to your [`.zed/settings.json`](https://zed.dev/docs/configuring-zed#settings-files):
.zed/settings.json
```json
{
"lsp": {
"tailwindcss-language-server": {
"settings": {
"classFunctions": ["cva", "cx"],
}
}
}
}
```
* Neovim
1. [Install the extension](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#tailwindcss)
2. Add the following configuration:
```lua
require 'lspconfig'.tailwindcss.setup({
settings = {
tailwindCSS = {
classFunctions = { "cva", "cx" },
},
},
})
```
* WebStorm
1. Check the version. Available for [WebStorm 2023.1](https://www.jetbrains.com/webstorm/whatsnew/#version-2023-1-tailwind-css-configuration) and later
2. Open the settings. Go to [Languages and Frameworks | Style Sheets | Tailwind CSS](https://www.jetbrains.com/help/webstorm/tailwind-css.html#ws_css_tailwind_configuration)
3. Add the following to your tailwind configuration
```json
{
"classFunctions": ["cva", "cx"]
}
```
## Merging classes
[Section titled “Merging classes”](#merging-classes)
By default, `cva` uses [`clsx`](https://github.com/lukeed/clsx) for conditional class joining. It does not resolve conflicting Tailwind CSS utilities.
Both configurations accept conditional objects and arrays.
### cn
[Section titled “cn”](#cn)
Install the [`cn`](https://www.npmjs.com/package/cn) package to join conditional classes and resolve Tailwind CSS conflicts in one function.
The [shadcn CLI](https://ui.shadcn.com/docs/registry/github) installs `cva@beta` with `cn` and writes `cva.config.ts` to your project root:
```sh
pnpm dlx shadcn@latest add joe-bell/cva/cn
```
To set it up by hand instead, add `cn` and export the configured functions from `cva.config.ts`:
```sh
pnpm add cn
```
cva.config.ts
```ts
import { defineConfig } from "cva/config";
import { cn as merge } from "cn";
export const { cva, cx: cn } = defineConfig({ cx: merge });
```
### tailwind-merge
[Section titled “tailwind-merge”](#tailwind-merge)
Install [`clsx`](https://github.com/lukeed/clsx) with [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) to preserve `cva`’s full `ClassValue` grammar while resolving Tailwind CSS conflicts:
```sh
pnpm add clsx tailwind-merge
```
cva.config.ts
```ts
import { defineConfig } from "cva/config";
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export const { cva, cx: cn } = defineConfig({
cx: (...inputs) => twMerge(clsx(inputs)),
});
```
Bare `twMerge`
Use bare `twMerge` when your authored values are strings or arrays. TypeScript rejects object syntax, but JavaScript has no compile-time protection. Exported configurations can expose tailwind-merge’s `ClassNameValue` in your library declarations.
```ts
import { defineConfig } from "cva/config";
import { twMerge } from "tailwind-merge";
export const { cva, cx: cn } = defineConfig({ cx: twMerge });
```
### Use either configuration
[Section titled “Use either configuration”](#use-either-configuration)
Use the configured functions throughout your project:
components/button.ts
```ts
import { cn, cva } from "../cva.config";
export const button = cva({
base: "font-semibold bg-gray-200 border rounded",
variants: {
intent: {
primary: "bg-blue-500 text-white border-transparent hover:bg-blue-600",
secondary: "bg-white text-gray-800 border-gray-400 hover:bg-gray-100",
},
},
defaultVariants: {
intent: "primary",
},
});
```
Both configurations produce the same output:
```ts
button();
// => "font-semibold border rounded bg-blue-500 text-white border-transparent hover:bg-blue-600"
cn("bg-gray-200", { "bg-blue-500": true });
// => "bg-blue-500"
```
For string-only authoring without conflict resolution, see [advanced concatenators](/beta/api-reference#advanced-concatenators) for `clsx/lite`.
# Polymorphism
> Apply cva classes to any HTML element, or roll your own render prop with Base UI's useRender.
`cva` components are polymorphic (and framework-agnostic) by default: apply the class to your preferred HTML element…
```tsx
import { button } from "./components/button";
export default () => (
Sign up
);
```
## Alternative approaches
[Section titled “Alternative approaches”](#alternative-approaches)
### React
[Section titled “React”](#react)
If you’d prefer to use a React-based API, `cva` strongly recommends using [Base UI’s `useRender` hook](https://base-ui.com/react/utils/use-render) to roll your own `render` prop.
```tsx
"use client";
import { cva, type VariantProps } from "cva";
import { useRender } from "@base-ui/react/use-render";
import { mergeProps } from "@base-ui/react/merge-props";
const _BUTTON_DEFAULT_TAG = "button" satisfies React.ElementType;
export interface ButtonProps
extends
useRender.ComponentProps,
VariantProps {}
const button = cva({
base: "button",
variants: {
intent: {
primary: "bg-blue-500 hover:bg-blue-600 border-transparent text-white",
secondary: "border-gray-400 bg-white text-gray-800 hover:bg-gray-100",
},
},
});
export function Button({
render,
intent = "primary",
className,
...props
}: ButtonProps) {
const defaultProps: useRender.ElementProps = {
className: button({ intent, className }),
// Consider data-attributes for debugging purposes
["data-button" as string]: "",
["data-intent" as string]: intent,
};
return useRender({
defaultTagName: _BUTTON_DEFAULT_TAG,
render,
props: mergeProps(defaultProps, props),
});
}
```
#### Usage
[Section titled “Usage”](#usage)
```tsx
import { Button } from "./components/button";
// Renders:
//
// Contact
//
export default () => }>Contact;
```
# Tools
> Read a component's variant names, values, and defaults with getSchema from cva/tools.
The `cva/tools` entry point holds the helpers that read a component’s variants back out at runtime. It’s a separate entry point, so an ESM bundler drops it from a build that never imports it.
## `getSchema`
[Section titled “getSchema”](#getschema)
Re-declaring variants for a Storybook story or prop table creates a second copy to keep in sync by hand. `getSchema` reads them from the component instead, so you declare each variant once:
```ts
import { cva } from "cva";
import { getSchema } from "cva/tools";
const button = cva({
base: "button",
variants: {
intent: { primary: "button--primary", secondary: "button--secondary" },
size: { small: "button--small", large: "button--large" },
},
defaultVariants: { intent: "primary", size: "small" },
});
getSchema(button);
// => {
// intent: { values: ["primary", "secondary"], defaultValue: "primary" },
// size: { values: ["small", "large"], defaultValue: "small" },
// }
```
The schema is fully typed: `values` narrows to the variant’s literal values, and `defaultValue` only appears when the component declares one. Hover the result in your editor and you see the same shape the runtime returns:
```ts
import { cva } from "cva";
import { getSchema } from "cva/tools";
const badge = cva({
base: "badge",
variants: {
tone: { info: "badge--info", warning: "badge--warning" },
round: { true: "badge--round", false: "badge--square" },
weight: { 400: "badge--regular", 700: "badge--bold" },
},
defaultVariants: { tone: "info" },
});
const schema = getSchema(badge);
// ^ {
// tone: {
// values: readonly ("info" | "warning")[];
// defaultValue: "info";
// };
// round: { values: readonly boolean[] };
// weight: { values: readonly (400 | 700)[] };
// }
```
Boolean and numeric variant keys come back as booleans and numbers, matching the props the component accepts rather than the object keys they were written as. [Internal variants](/beta/getting-started/variants#internal-variants) are omitted.
For the full signature, see the [API reference](/beta/api-reference#getschema).
# TypeScript
> Extract variant types with VariantProps and require specific variants with TypeScript utility types.
## Extracting variant types
[Section titled “Extracting variant types”](#extracting-variant-types)
`cva` offers the `VariantProps` helper to extract variant types.
`VariantProps` omits variant names prefixed with `_`; see [Internal variants](/beta/getting-started/variants#internal-variants).
components/button.ts
```ts
import type { VariantProps } from "cva";
import { cva, cx } from "cva";
/**
* Button
*/
export type ButtonProps = VariantProps;
export const button = cva(/* … */);
```
## Required variants
[Section titled “Required variants”](#required-variants)
To keep the API small and unopinionated, `cva` **doesn’t** offer a built-in solution for setting required variants.
Instead, we recommend using TypeScript’s [Utility Types](https://www.typescriptlang.org/docs/handbook/utility-types.html):
components/button.ts
```ts
import { cva, type VariantProps } from "cva";
export type ButtonVariantProps = VariantProps;
export const buttonVariants = cva({
base: "…",
variants: {
optional: { a: "…", b: "…" },
required: { a: "…", b: "…" },
},
});
/**
* Button
*/
export interface ButtonProps
extends
Omit,
Required> {}
export const button = (props: ButtonProps) => buttonVariants(props);
// ❌ TypeScript Error:
// Argument of type "{}": is not assignable to parameter of type "ButtonProps".
// Property "required" is missing in type "{}" but required in type
// "ButtonProps".
button({});
// ✅
button({ required: "a" });
```
# Variants
> Create variants, compound variants, and disable variants with cva@beta.
## Creating variants
[Section titled “Creating variants”](#creating-variants)
Note
Although `cva` is a [**tiny**](https://bundlephobia.com/package/cva) 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"
```
## Internal variants
[Section titled “Internal 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`](/beta/getting-started/typescript#extracting-variant-types) and [`getSchema`](/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;
// => { 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,
VariantProps {
active?: boolean;
}
// `_intent` never appears in `Props`, so a consumer can't set it.
function Button({ active, size, className, ...props }: Props) {
return (
);
}
```
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](/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
[Section titled “Compound variants”](#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
[Section titled “Targeting multiple variant conditions”](#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
[Section titled “Disabling variants”](#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"
```
# What's New?
> What changed in cva@1.0, defineConfig, composes, the cva/tools entry point, and other breaking changes since class-variance-authority@0.x.
What’s changed since `class-variance-authority@0.*`?
## Requirements
[Section titled “Requirements”](#requirements)
TypeScript projects using `cva@beta` need TypeScript 6.0 or later. JavaScript usage is unaffected.
## Features
[Section titled “Features”](#features)
### 1. `defineConfig`
[Section titled “1. defineConfig”](#1-defineconfig)
Roll your own `cva` via the new [`defineConfig` API in `cva/config`](/beta/api-reference#cvaconfig).
Use `cva`/`cx` with `cn`, `tailwind-merge`, or your own concatenator via the [`cx` option](/beta/getting-started/installation#merging-classes).
### 2. `composes`
[Section titled “2. composes”](#2-composes)
Shallow merge one or more `cva` components into a single component via the new [`composes` property](/beta/getting-started/composing-components).
This [replaces the `compose` function](#1-compose--composes).
### 3. `getSchema`
[Section titled “3. getSchema”](#3-getschema)
Extract a plain-object schema (variant names, values and defaults) from a `cva` component via the new [`cva/tools`](/beta/getting-started/tools) entry point’s `getSchema`. Use it to generate Storybook controls or any other UI that reads a component’s variants.
### 4. Internal variants
[Section titled “4. Internal variants”](#4-internal-variants)
A variant name prefixed with `_` is now treated as [internal](/beta/getting-started/variants#internal-variants). It stays off a wrapping component’s public props because `VariantProps` and `getSchema` omit it, while the `cva` component still accepts it.
Breaking change
If a component already declared a variant with a leading underscore (`_state`, `_internal`, etc.), that variant now disappears from `VariantProps` and from `getSchema` results. The component still accepts it directly, but you’ll need to rename it if you exposed it as a public prop.
### 5. Bring your own concatenator with `cva/config`
[Section titled “5. Bring your own concatenator with cva/config”](#5-bring-your-own-concatenator-with-cvaconfig)
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.
## Deprecations
[Section titled “Deprecations”](#deprecations)
### 1. `compose` → `composes`
[Section titled “1. compose → composes”](#1-compose--composes)
The `compose` method is deprecated in favor of the `composes` property inside `cva`. Pass a single component directly, or pass multiple components as an array.
```diff
-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] });
```
Note
`composes` isn’t always a byte-for-byte swap. When composed components declare conflicting `defaultVariants` for the same key, `composes` applies a single last-wins default to every component. `compose` let each component resolve its own default instead. See [Extending variants](/beta/getting-started/composing-components#extending-variants).
See [Composing Components](/beta/getting-started/composing-components) for more details and migration examples.
## Enhancements
[Section titled “Enhancements”](#enhancements)
### 1. `class-variance-authority` → `cva`
[Section titled “1. class-variance-authority → cva”](#1-class-variance-authority--cva)
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`!
### 2. `cva` now accepts a single parameter
[Section titled “2. cva now accepts a single parameter”](#2-cva-now-accepts-a-single-parameter)
Base styles are now applied via the named `base` property.
```diff
-import { cva } from "class-variance-authority";
+import { cva } from "cva";
const component = cva({ base: "your-base-class" });
```
### 3. Goodbye `null`
[Section titled “3. Goodbye null”](#3-goodbye-null)
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](https://github.com/joe-bell/cva/discussions/97).
Instead, we now recommend explicitly [rolling your own `unset` variant](/beta/getting-started/variants#disabling-variants).
### 4. Clearer type guards
[Section titled “4. Clearer type guards”](#4-clearer-type-guards)
`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.
### 5. Faster component calls
[Section titled “5. Faster component calls”](#5-faster-component-calls)
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 about 2.1 kB (brotli), up from 1.4 kB. The precomputed tables are the difference.
Thanks to [@fveracoechea](https://github.com/fveracoechea) for inspiring this work! See [the pull request](https://github.com/joe-bell/cva/pull/419) for the full numbers.
### 6. Configuration is read when you create the component
[Section titled “6. Configuration is read when you create the component”](#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` and `hooks` from your `defineConfig` options, which stay live. That is where most of the speed above comes from.
Breaking change
Treat a config, and everything it references, as immutable once you’ve created the component. Mutating it afterwards is unsupported, and how much of the change shows up is undefined. Create a new component instead:
```diff
-const config = {
base: "button",
variants: { intent: { primary: "bg-blue-500" } },
-};
-const button = cva(config);
-config.variants.intent.primary = "bg-indigo-500";
+const button = cva({
base: "button",
variants: { intent: { primary: "bg-blue-500" } },
+});
+const indigoButton = cva({
base: "button",
variants: { intent: { primary: "bg-indigo-500" } },
+});
```
Composition follows the same rule: a component pushed onto a `composes` array after the fact won’t show up, and editing a composed child’s own config won’t update the parent’s merged variants. Recreate the parent.
`component.config` still exposes the merged `variants` and `defaultVariants` as fresh objects, but it’s internal: reading it is fine, mutating it is unsupported and may partly show up in later output. Frozen configs work, and `cva` never mutates or freezes anything you pass it.
### 7. Faster type checking
[Section titled “7. Faster type checking”](#7-faster-type-checking)
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](https://github.com/joe-bell/cva/pull/420) for the full numbers.
# Tutorials
> Community videos, podcasts, and articles about cva.
## YouTube
[Section titled “YouTube”](#youtube)
* 
# [Class Variance Authority (CVA) Quickstart](https://www.youtube.com/watch?v=kHQNK2jU_TQ)
Coding in Public•15th September 2023
* 
# [Authoring Components with CVA + tailwindcss](https://www.youtube.com/watch?v=qGQRdCg6JRQ)
React Tips with Brooks Lybrand•29th August 2023
* 
# [Creating High-Quality React Components: Best Practices for Reusability](https://www.youtube.com/watch?v=eXRlVpw1SIQ)
Josh tried coding•26th February 2023
* 
# [Building a design system in Next.js with Tailwind!](https://www.youtube.com/watch?v=kTSwLLFa3WM)
mewtru•4th January 2023
* 
# [Large Tailwind Components — What to do About All Those Classes](https://www.youtube.com/watch?v=B6FrDu2Qbt0)
frontendfyi•21st November 2022
* 
# [Tru Narla: Building a design system in Next.js with Tailwind](https://www.youtube.com/watch?v=T-Zv73yZ_QI)
mewtru•8th October 2022
## Audio
[Section titled “Audio”](#audio)
* [JS Party – Episode #277](https://changelog.com/jsparty/277) (25th May 2023)
## Articles
[Section titled “Articles”](#articles)
* ["Building a design system using Solidjs, Typescript, SCSS, CSS Variables and Vite"](https://dev.to/yaldram/building-a-design-system-using-solidjs-typescript-scss-css-variables-and-vite-setup-45nj) by Arsalan Ahmed Yaldram(15th April 2023)
* ["Building a design system with dark mode using React, Typescript, scss, cva and Vite"](https://dev.to/yaldram/build-a-design-system-with-dark-mode-using-react-typescript-scss-cva-and-vite-setup-5778) by Arsalan Ahmed Yaldram(20th March 2023)
Created a tutorial?
Add your content to the [tutorials directory](https://github.com/joe-bell/cva/tree/main/docs/src/content/tutorials).
# 11ty
> Build a cva button component in an 11ty template with Tailwind CSS.
This example builds a `button` component with `cva` in an 11ty template.
## Tailwind
[Section titled “Tailwind”](#tailwind)
button.11ty.js
```js
const { cva } = require("class-variance-authority");
// ⚠️ Disclaimer: Use of Tailwind CSS is optional
const button = cva("button", {
variants: {
intent: {
primary: [
"bg-blue-500",
"text-white",
"border-transparent",
"hover:bg-blue-600",
],
secondary: [
"bg-white",
"text-gray-800",
"border-gray-400",
"hover:bg-gray-100",
],
},
size: {
small: ["text-sm", "py-1", "px-2"],
medium: ["text-base", "py-2", "px-4"],
},
},
compoundVariants: [{ intent: "primary", size: "medium", class: "uppercase" }],
defaultVariants: {
intent: "primary",
size: "medium",
},
});
module.exports = function ({ label, intent, size }) {
return ``;
};
```
# Astro
> A cva button component built with Astro and Tailwind CSS.
This example builds a `button` component with `cva` in an Astro project.
## Tailwind CSS
[Section titled “Tailwind CSS”](#tailwind-css)
[View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/astro-with-tailwindcss/src/components/button.astro)
# BEM
> Use cva to apply BEM class names instead of utility classes.
This example applies BEM class names with `cva` instead of utility classes.
styles.css
```css
.button {
/* */
}
.button--primary {
/* */
}
.button--secondary {
/* */
}
.button--small {
/* */
}
.button--medium {
/* */
}
.button--primary-medium {
/* */
}
```
```ts
import { cva } from "class-variance-authority";
const button = cva("button", {
variants: {
intent: {
primary: "button--primary",
secondary: "button--secondary",
},
size: {
small: "button--small",
medium: "button--medium",
},
},
compoundVariants: [
{ intent: "primary", size: "medium", class: "button--primary-medium" },
],
defaultVariants: {
intent: "primary",
size: "medium",
},
});
button();
// => "button button--primary button--medium button--primary-medium"
button({ intent: "secondary", size: "small" });
// => "button button--secondary button--small"
```
# Other Use Cases
> Use cva to manage variant-driven strings beyond class names, such as dynamic text content.
Although primarily designed for handling class names, at its core `cva` is a fancy way of managing a string…
## Dynamic text content
[Section titled “Dynamic text content”](#dynamic-text-content)
```ts
import { cva } from "class-variance-authority";
const greeter = cva("Good morning!", {
variants: {
isLoggedIn: {
true: "Here's a secret only logged in users can see",
false: "Log in to find out more…",
},
},
defaultVariants: {
isLoggedIn: false,
},
});
greeter();
// => "Good morning! Log in to find out more…"
greeter({ isLoggedIn: true });
// => "Good morning! Here's a secret only logged in users can see"
```
# React with CSS Modules
> A cva button component styled with CSS Modules in React.
This example builds a `button` component with `cva`, styled with CSS Modules, in a React project.
[View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/react-with-css-modules/src/components/button/button.tsx)
# React with Tailwind CSS
> A cva button component built with React and Tailwind CSS.
This example builds a `button` component with `cva` and Tailwind CSS in a React project.
[View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/react-with-tailwindcss/src/components/button/button.tsx)
# Svelte
> A cva button component built with Svelte.
This example builds a `button` component with `cva` in a Svelte project. Open the sandbox below to explore the full source.
[View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/svelte/src/components/button.svelte)
# Vue
> A cva button component built with Vue.
This example builds a `Button` component with `cva` in a Vue project. Open the sandbox below to explore the full source.
[View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/vue/src/components/Button.vue)
# FAQs
> Answers to common questions about cva's API design, including responsive variants and styled-component APIs.
## Why don’t you provide a `styled` API?
[Section titled “Why don’t you provide a styled API?”](#why-dont-you-provide-a-styled-api)
Long story short: it’s unnecessary.
`cva` encourages you to think of components as traditional CSS classes:
* Less JavaScript is better
* They’re framework agnostic; truly reusable
* Polymorphism is free: apply the class to your preferred HTML element
* Less opinionated; you’re free to build components with `cva` however you’d like
## How can I create [responsive variants like Stitches.js](https://stitches.dev/docs/responsive-styles#responsive-variants)?
[Section titled “How can I create responsive variants like Stitches.js?”](#how-can-i-create-responsive-variants-like-stitchesjs)
You can’t.
`cva` doesn’t know about how you choose to apply CSS classes, and it doesn’t want to.
We recommend either:
* Showing/hiding elements with different variants, based on your preferred breakpoint.
Example: With Tailwind
```tsx
export const Example = () => (
<>
>
);
```
* Create a bespoke variant that changes based on the breakpoint.
*e.g. `button({ intent: "primaryUntilMd" })`*
This is something I’ve been thinking about since the project’s inception, and I’ve gone back and forth many times on the idea of building it. It’s a large undertaking and brings all the complexity of supporting many different build tools and frameworks.
In my experience, “responsive variants” are typically rare, and hiding/showing different elements is usually good enough to get by.
To be frank, I’m probably not going to build/maintain a solution unless someone periodically gives me a thick wad of cash to do so, and even then I’d probably rather spend my free time living my life.
# Composing Components
> Extend and concatenate cva components with cx.
Tip
`cva@beta` has first-class composition via the `composes` property. See [Composing components](/beta/getting-started/composing-components).
While `cva` doesn’t offer a built-in method for composing components together, it does offer the tools to *extend* components on your own terms…
For example; two `cva` components, concatenated together with `cx`:
components/card.ts
```ts
import type { VariantProps } from "class-variance-authority";
import { cva, cx } from "class-variance-authority";
/**
* Box
*/
export type BoxProps = VariantProps;
export const box = cva(["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,
},
});
/**
* Card
*/
type CardBaseProps = VariantProps;
const cardBase = cva(["card", "border-solid", "border-slate-300", "rounded"], {
variants: {
shadow: {
md: "drop-shadow-md",
lg: "drop-shadow-lg",
xl: "drop-shadow-xl",
},
},
});
export interface CardProps extends BoxProps, CardBaseProps {}
export const card = ({ margin, padding, shadow }: CardProps = {}) =>
cx(box({ margin, padding }), cardBase({ shadow }));
```
# Extending Components
> Pass extra classes to a cva component with the class or className prop.
All `cva` components provide an optional `class` **or** `className` prop, which can be used to pass additional classes to the component.
components/button.ts
```ts
import { cva } from "class-variance-authority";
const button = cva("font-semibold");
button({ class: "m-4" });
// => "font-semibold m-4"
button({ className: "m-4" });
// => "font-semibold m-4"
```
# Installation
> Install class-variance-authority and configure Tailwind CSS IntelliSense and style-conflict handling.
* pnpm
```sh
pnpm i class-variance-authority
```
* npm
```sh
npm i class-variance-authority
```
* yarn
```sh
yarn add class-variance-authority
```
* bun
```sh
bun add class-variance-authority
```
* deno
```sh
deno add class-variance-authority
```
Do I have to write such a long package name?
Unfortunately, for a little bit longer, yes. Originally, the plan was to publish the package as `cva`, but someone had already registered that name on npm and [marked it as a “placeholder”](https://www.npmjs.com/package/cva).
On 2022/02/16, GitHub transferred NPM ownership of `cva` to [Joe Bell](https://joebell.studio). This shorter name will be used from v1 onwards.
In the meantime, you can always alias the package for your convenience…
1. Alias the package with [`npm install`](https://docs.npmjs.com/cli/v6/commands/npm-install)
```sh
npm i cva@npm:class-variance-authority
```
2. Then import like so:
```ts
import { cva } from "cva";
// …
```
## Tailwind CSS
[Section titled “Tailwind CSS”](#tailwind-css)
If you’re a Tailwind user, here are some additional (optional) steps to get the most out of `cva`:
### IntelliSense
[Section titled “IntelliSense”](#intellisense)
You can enable autocompletion inside `cva` using the steps below:
* Visual Studio Code
1. [Install the “Tailwind CSS IntelliSense” Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)
2. Add the following to your [`settings.json`](https://code.visualstudio.com/docs/getstarted/settings):
```json
{
"tailwindCSS.classFunctions": ["cva", "cx"]
}
```
* Zed
Add the following to your [`.zed/settings.json`](https://zed.dev/docs/configuring-zed#settings-files):
```json
{
"lsp": {
"tailwindcss-language-server": {
"settings": {
"classFunctions": ["cva", "cx"],
}
}
}
}
```
* Neovim
1. [Install the extension](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#tailwindcss)
2. Add the following configuration:
```lua
require 'lspconfig'.tailwindcss.setup({
settings = {
tailwindCSS = {
classFunctions = { "cva", "cx" },
},
},
})
```
* WebStorm
1. Check the version. Available for [WebStorm 2023.1](https://www.jetbrains.com/webstorm/whatsnew/#version-2023-1-tailwind-css-configuration) and later
2. Open the settings. Go to [Languages and Frameworks | Style Sheets | Tailwind CSS](https://www.jetbrains.com/help/webstorm/tailwind-css.html#ws_css_tailwind_configuration)
3. Add the following to your tailwind configuration
```json
{
"classFunctions": ["cva", "cx"]
}
```
### Handling style conflicts
[Section titled “Handling style conflicts”](#handling-style-conflicts)
Although `cva`’s API is designed to help you avoid styling conflicts, there’s still a small margin of error.
If you’re keen to lift that burden altogether, check out the wonderful [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) package.
For bulletproof components, wrap your `cva` component with `twMerge`.
Example with tailwind-merge
```ts
import { cva, type VariantProps } from "class-variance-authority";
import { twMerge } from "tailwind-merge";
const buttonVariants = cva(["your", "base", "classes"], {
variants: {
intent: {
primary: ["your", "primary", "classes"],
},
},
defaultVariants: {
intent: "primary",
},
});
export interface ButtonVariants extends VariantProps {}
export const button = (variants: ButtonVariants) =>
twMerge(buttonVariants(variants));
```
# TypeScript
> Extract variant types with VariantProps and require specific variants with TypeScript utility types.
## Extracting variant types
[Section titled “Extracting variant types”](#extracting-variant-types)
`cva` offers the `VariantProps` helper to extract variant types
components/button.ts
```ts
import type { VariantProps } from "class-variance-authority";
import { cva, cx } from "class-variance-authority";
/**
* Button
*/
export type ButtonProps = VariantProps;
export const button = cva(/* … */);
```
## Required variants
[Section titled “Required variants”](#required-variants)
To keep the API small and unopinionated, `cva` **doesn’t** offer a built-in solution for setting required variants.
Instead, we recommend using TypeScript’s [Utility Types](https://www.typescriptlang.org/docs/handbook/utility-types.html):
components/button.ts
```ts
import { cva, type VariantProps } from "class-variance-authority";
export type ButtonVariantProps = VariantProps;
export const buttonVariants = cva("…", {
variants: {
optional: { a: "…", b: "…" },
required: { a: "…", b: "…" },
},
});
/**
* Button
*/
export interface ButtonProps
extends
Omit,
Required> {}
export const button = (props: ButtonProps) => buttonVariants(props);
// ❌ TypeScript Error:
// Argument of type "{}": is not assignable to parameter of type "ButtonProps".
// Property "required" is missing in type "{}" but required in type
// "ButtonProps".
button({});
// ✅
button({ required: "a" });
```
# Variants
> Create variants, compound variants, and default variants with cva.
## Creating variants
[Section titled “Creating variants”](#creating-variants)
Note
Although `cva` is a [**tiny**](https://bundlephobia.com/package/class-variance-authority) 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 "class-variance-authority";
const button = cva(["font-semibold", "border", "rounded"], {
variants: {
intent: {
primary: ["bg-blue-500", "text-white", "border-transparent"],
// **or**
// primary: "bg-blue-500 text-white border-transparent hover:bg-blue-600",
secondary: ["bg-white", "text-gray-800", "border-gray-400"],
},
size: {
small: ["text-sm", "py-1", "px-2"],
medium: ["text-base", "py-2", "px-4"],
},
// `boolean` variants are also supported!
disabled: {
false: null,
true: ["opacity-50", "cursor-not-allowed"],
},
},
compoundVariants: [
{
intent: "primary",
disabled: false,
class: "hover:bg-blue-600",
},
{
intent: "secondary",
disabled: false,
class: "hover:bg-gray-100",
},
{
intent: "primary",
size: "medium",
// **or** if you're a React.js user, `className` may feel more consistent:
// className: "uppercase"
class: "uppercase",
},
],
defaultVariants: {
intent: "primary",
size: "medium",
disabled: false,
},
});
button();
// => "font-semibold border rounded bg-blue-500 text-white border-transparent text-base py-2 px-4 hover:bg-blue-600 uppercase"
button({ disabled: true });
// => "font-semibold border rounded bg-blue-500 text-white border-transparent text-base py-2 px-4 opacity-50 cursor-not-allowed uppercase"
button({ intent: "secondary", size: "small" });
// => "font-semibold border rounded bg-white text-gray-800 border-gray-400 text-sm py-1 px-2 hover:bg-gray-100"
```
## Compound variants
[Section titled “Compound variants”](#compound-variants)
Variants that apply when multiple other variant conditions are met.
components/button.ts
```ts
import { cva } from "class-variance-authority";
const button = cva("…", {
variants: {
intent: { primary: "…", secondary: "…" },
size: { small: "…", medium: "…" },
},
compoundVariants: [
// Applied via:
// `button({ intent: "primary", size: "medium" })`
{
intent: "primary",
size: "medium",
class: "…",
},
],
});
```
### Targeting multiple variant conditions
[Section titled “Targeting multiple variant conditions”](#targeting-multiple-variant-conditions)
components/button.ts
```ts
import { cva } from "class-variance-authority";
const button = cva("…", {
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: "…",
},
],
});
```
# Tutorials
> Community videos, podcasts, and articles about cva.
## YouTube
[Section titled “YouTube”](#youtube)
* 
# [Class Variance Authority (CVA) Quickstart](https://www.youtube.com/watch?v=kHQNK2jU_TQ)
Coding in Public•15th September 2023
* 
# [Authoring Components with CVA + tailwindcss](https://www.youtube.com/watch?v=qGQRdCg6JRQ)
React Tips with Brooks Lybrand•29th August 2023
* 
# [Creating High-Quality React Components: Best Practices for Reusability](https://www.youtube.com/watch?v=eXRlVpw1SIQ)
Josh tried coding•26th February 2023
* 
# [Building a design system in Next.js with Tailwind!](https://www.youtube.com/watch?v=kTSwLLFa3WM)
mewtru•4th January 2023
* 
# [Large Tailwind Components — What to do About All Those Classes](https://www.youtube.com/watch?v=B6FrDu2Qbt0)
frontendfyi•21st November 2022
* 
# [Tru Narla: Building a design system in Next.js with Tailwind](https://www.youtube.com/watch?v=T-Zv73yZ_QI)
mewtru•8th October 2022
## Audio
[Section titled “Audio”](#audio)
* [JS Party – Episode #277](https://changelog.com/jsparty/277) (25th May 2023)
## Articles
[Section titled “Articles”](#articles)
* ["Building a design system using Solidjs, Typescript, SCSS, CSS Variables and Vite"](https://dev.to/yaldram/building-a-design-system-using-solidjs-typescript-scss-css-variables-and-vite-setup-45nj) by Arsalan Ahmed Yaldram(15th April 2023)
* ["Building a design system with dark mode using React, Typescript, scss, cva and Vite"](https://dev.to/yaldram/build-a-design-system-with-dark-mode-using-react-typescript-scss-cva-and-vite-setup-5778) by Arsalan Ahmed Yaldram(20th March 2023)
Created a tutorial?
Add your content to the [tutorials directory](https://github.com/joe-bell/cva/tree/main/docs/src/content/tutorials).