>
);
```
* 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 combined = cva({ composes: [a, b] });
combined();
// => "a b b-secondary"
```
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.
# 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)](../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 and configure Tailwind CSS IntelliSense and tailwind-merge.
* 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"]
}
```
### Handling style conflicts
[Section titled “Handling style conflicts”](#handling-style-conflicts)
If you want to merge Tailwind CSS classes without conflicts, you may wish to [roll-your-own `cva`](../api-reference#defineconfig) with the [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) package:
Example with tailwind-merge
cva.config.ts
```ts
import { defineConfig } from "cva";
import { twMerge } from "tailwind-merge";
export const { cva, cx } = defineConfig({
hooks: {
onComplete: (className) => twMerge(className),
},
});
```
components/button.ts
```ts
import { cx, cva } from "../cva.config";
export const button = cva({
// 1. `twMerge` strips out `bg-gray-200`…
base: "font-semibold bg-gray-200 border rounded",
variants: {
intent: {
// 2. …as variant `bg-*` values take precedence
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",
},
});
button();
// => "font-semibold border rounded bg-blue-500 text-white border-transparent hover:bg-blue-600"
cx("bg-gray-200", "bg-blue-500");
// => "bg-blue-500"
```
# 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;
```
# 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 "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" });
```
# Utilities
> Extract a component's variant names, values, and defaults with getSchema.
## `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` extracts them from the component instead, so your config stays the single source of truth:
```ts
import { cva, getSchema } from "cva";
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.
For the full signature, see the [API reference](/beta/api-reference#getschema).
# 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"
```
## 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, getSchema, and other breaking changes since class-variance-authority@0.x.
***
What’s changed since `class-variance-authority@0.*`?
## Features
[Section titled “Features”](#features)
### 1. `defineConfig`
[Section titled “1. defineConfig”](#1-defineconfig)
Extend `cva` via the new [`defineConfig` API](/beta/api-reference#defineconfig).
Use `cva`/`cx` with `tailwind-merge` via the new [`hooks.onComplete` option](/beta/getting-started/installation#handling-style-conflicts).
### 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 [`getSchema` utility](/beta/getting-started/utilities). Use it to generate Storybook controls or any other UI that reads a component’s variants.
## 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.
# 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-small {
/* */
}
```
```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-small" },
],
defaultVariants: {
intent: "primary",
size: "medium",
},
});
button();
// => "button button--primary button--medium button--primary-small"
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).