This is the full developer documentation for cva # Class Variance Authority > Build type-safe, variant-driven class names for any styling approach, with first-class Tailwind CSS support. ![cva logo](/assets/img/wallpaper-4k.png) `cva` is a tiny library for building type-safe, variant-driven class names with Tailwind CSS or any other styling approach. CSS-in-TS libraries such as [Stitches](https://stitches.dev/docs/variants) and [Vanilla Extract](https://vanilla-extract.style/documentation/api/style-variants/) handle type-safe UI variants without you managing class names or stylesheet composition by hand. CSS-in-TS isn’t for everyone, though. You may need full control over your stylesheet output, use a framework such as Tailwind CSS, or prefer writing your own CSS. Creating variants with the “traditional” CSS approach can become an arduous task: manually matching classes to props, and manually adding types. `cva` takes away those pain points, so you can focus on building your UI. ## Sponsors [Section titled “Sponsors”](#sponsors) Want to support this project? `cva` is a privately-maintained **free** open-source project.\ Please consider [**sponsoring**](/sponsors) my work to contribute towards development efforts financially. ### Stats [Section titled “Stats”](#stats) 85.4 million weekly downloads Across all packages | Package | Downloads | | ---------------------------------------------------------------------------------- | --------- | | [class-variance-authority](https://www.npmjs.com/package/class-variance-authority) | 82.9M | | [cva@beta](https://www.npmjs.com/package/cva/v/beta) | 2.4M | 596 B Minified and compressed with Brotli. ## Acknowledgments [Section titled “Acknowledgments”](#acknowledgments) * [**Stitches**](https://stitches.dev/) ([WorkOS](https://workos.com))\ Huge thanks to the WorkOS team for pioneering the `variants` API movement: your open-source contributions are immensely appreciated * [**cx**](https://github.com/joe-bell/cx)\ Some of the ideas behind `cva` first appeared in `cx`, a type-safe class concatenation utility with reusable shortcuts, in October 2020. * [**clb**](https://github.com/crswll/clb) ([Bill Criswell](https://github.com/crswll))\ `cva` began with the intention of merging into the wonderful [`clb`](https://github.com/crswll/clb) library, but after some discussion with Bill, we felt a separate project made more sense.\ I’m so grateful to Bill for sharing his work publicly and for getting me excited about building a type-safe variants API for classes. If you have a moment, please go and [star the project on GitHub](https://github.com/crswll/clb). Thank you Bill! * [**clsx**](https://github.com/lukeed/clsx) ([Luke Edwards](https://github.com/lukeed))\ Previously, this project surfaced a custom `cx` utility for flattening classes, but it lacked the ability to handle variadic arguments or objects. [clsx](https://github.com/lukeed/clsx) provided those extra features with quite literally zero increase to the bundle size: a no-brainer to switch! * [**Vanilla Extract**](http://vanilla-extract.style) ([Seek](https://github.com/seek-oss)) ## Downloads [Section titled “Downloads”](#downloads) * [Wallpaper](/assets/img/wallpaper-4k.png) ## License [Section titled “License”](#license) [Apache-2.0 License](https://github.com/joe-bell/cva/blob/main/LICENSE) © [Joe Bell](https://joebell.studio) # API Reference > Reference for the cva and cx functions. ## `cva` [Section titled “cva”](#cva) Builds a `cva` component ```ts const component = cva("base", options); ``` ### Parameters [Section titled “Parameters”](#parameters) 1. `base`: the base class name (`string`, `string[]` or other [`clsx` value](https://github.com/lukeed/clsx#input)) 2. `options` *(optional)* * `variants`: your variants schema * `compoundVariants`: variants based on a combination of previously defined variants * `defaultVariants`: set default values for previously defined variants\ *note: these default values can be removed completely by setting the variant as `null`* ### Returns [Section titled “Returns”](#returns) A `cva` component function ## `cx` [Section titled “cx”](#cx) Concatenates class names (an alias of [`clsx`](https://github.com/lukeed/clsx)) ```ts const className = cx(classes); ``` ### Parameters [Section titled “Parameters”](#parameters-1) * `classes`: array of classes to be concatenated ([see `clsx` usage](https://github.com/lukeed/clsx#input)) ### Returns [Section titled “Returns”](#returns-1) `string` # Class Variance Authority > Build type-safe, variant-driven class names for any styling approach, with first-class Tailwind CSS support. ![cva logo](/assets/img/wallpaper-4k.png) `cva` is a tiny library for building type-safe, variant-driven class names with Tailwind CSS or any other styling approach. CSS-in-TS libraries such as [Stitches](https://stitches.dev/docs/variants) and [Vanilla Extract](https://vanilla-extract.style/documentation/api/style-variants/) handle type-safe UI variants without you managing class names or stylesheet composition by hand. CSS-in-TS isn’t for everyone, though. You may need full control over your stylesheet output, use a framework such as Tailwind CSS, or prefer writing your own CSS. Creating variants with the “traditional” CSS approach can become an arduous task: manually matching classes to props, and manually adding types. `cva` takes away those pain points, so you can focus on building your UI. ## Sponsors [Section titled “Sponsors”](#sponsors) Want to support this project? `cva` is a privately-maintained **free** open-source project.\ Please consider [**sponsoring**](/sponsors) my work to contribute towards development efforts financially. ### Stats [Section titled “Stats”](#stats) 85.4 million weekly downloads Across all packages | Package | Downloads | | ---------------------------------------------------------------------------------- | --------- | | [class-variance-authority](https://www.npmjs.com/package/class-variance-authority) | 82.9M | | [cva@beta](https://www.npmjs.com/package/cva/v/beta) | 2.4M | 1.64 KB Minified and compressed with Brotli. ## Acknowledgments [Section titled “Acknowledgments”](#acknowledgments) * [**Stitches**](https://stitches.dev/) ([WorkOS](https://workos.com))\ Huge thanks to the WorkOS team for pioneering the `variants` API movement: your open-source contributions are immensely appreciated * [**cx**](https://github.com/joe-bell/cx)\ Some of the ideas behind `cva` first appeared in `cx`, a type-safe class concatenation utility with reusable shortcuts, in October 2020. * [**clb**](https://github.com/crswll/clb) ([Bill Criswell](https://github.com/crswll))\ `cva` began with the intention of merging into the wonderful [`clb`](https://github.com/crswll/clb) library, but after some discussion with Bill, we felt a separate project made more sense.\ I’m so grateful to Bill for sharing his work publicly and for getting me excited about building a type-safe variants API for classes. If you have a moment, please go and [star the project on GitHub](https://github.com/crswll/clb). Thank you Bill! * [**clsx**](https://github.com/lukeed/clsx) ([Luke Edwards](https://github.com/lukeed))\ Previously, this project surfaced a custom `cx` utility for flattening classes, but it lacked the ability to handle variadic arguments or objects. [clsx](https://github.com/lukeed/clsx) provided those extra features with quite literally zero increase to the bundle size: a no-brainer to switch! * [**Vanilla Extract**](http://vanilla-extract.style) ([Seek](https://github.com/seek-oss)) ## Downloads [Section titled “Downloads”](#downloads) * [Wallpaper](/assets/img/wallpaper-4k.png) ## License [Section titled “License”](#license) [Apache-2.0 License](https://github.com/joe-bell/cva/blob/main/LICENSE) © [Joe Bell](https://joebell.studio) # API Reference > API reference for cva, cva/config, cva/tools, and cva/tailwindcss. ## `cva` [Section titled “cva”](#cva) Builds a `cva` component ```ts import { cva } from "cva"; const component = cva(options); ``` Treat component configuration as immutable `cva` reads `options` during creation. Treat it, and everything it references, as immutable afterwards. To change a component’s configuration, create a new component. A call reads only the props you pass it, plus the `cx` you gave [`defineConfig`](#defineconfig), which stays live. Getters with side effects, and changing the props object while the call is running, are unsupported. ### Parameters [Section titled “Parameters”](#parameters) 1. `options` * `base`: the base class name (`string`, `string[]` or other [`clsx` value](https://github.com/lukeed/clsx#input)) * `variants`: your [variants schema](/beta/getting-started/variants) * `compoundVariants`: variants based on a combination of previously defined variants * `defaultVariants`: set default values for previously defined variants * `composes`: shallow merge one or more other `cva` components into this one, as a single component or an array (see [Composing Components](/beta/getting-started/composing-components)) ### Returns [Section titled “Returns”](#returns) A `cva` component function ## `cx` [Section titled “cx”](#cx) Concatenates class names (an alias of [`clsx`](https://github.com/lukeed/clsx); swap in your own concatenator via [`cva/config`](#cvaconfig)). ```ts import { cx } from "cva"; cx("button", "rounded"); ``` Use cx for conditional class names Use the included `cx` instead of adding `clsx` or `classnames` for conditional class names. See the [`clsx` documentation](https://github.com/lukeed/clsx#usage) for examples of strings, arrays, and conditional objects. If you configure a different concatenator, follow its documentation for supported inputs; see [handling class conflicts](/beta/getting-started/installation#handling-class-conflicts) for the Tailwind CSS options. ### Parameters [Section titled “Parameters”](#parameters-1) * Zero or more class values. The preset accepts [`clsx` values](https://github.com/lukeed/clsx#input); `cva/config` uses the configured concatenator’s grammar ### Returns [Section titled “Returns”](#returns-1) `string` ## `cva/tailwindcss` [Section titled “cva/tailwindcss”](#cvatailwindcss) Custom Tailwind CSS variants, for use with or without `cva`. Import it after Tailwind CSS to add the `base:` variant. ### `base:` [Section titled “base:”](#base) Marks a utility as a component default. Ordinary utilities override `base:` utilities through the CSS cascade, and `base:` utilities still override browser defaults and Tailwind’s `base` and `components` layers. See [cva/tailwindcss](/beta/getting-started/installation#cvatailwindcss) for setup and limitations. ## `cva/tools` [Section titled “cva/tools”](#cvatools) ### `getSchema` [Section titled “getSchema”](#getschema) Extracts a plain-object schema (variant names, possible values, and default values) from a `cva` component. Use it to generate Storybook controls, documentation, or any other UI that reads a component’s variants without re-declaring them. See [Tools](/beta/getting-started/tools) for use cases. ```ts import { cva } from "cva"; import { getSchema } from "cva/tools"; const button = cva({ base: "button", variants: { intent: { primary: "button--primary", secondary: "button--secondary" }, disabled: { true: "button--disabled", false: "button--enabled" }, }, defaultVariants: { intent: "primary", disabled: false }, }); getSchema(button); // => { // intent: { values: ["primary", "secondary"], defaultValue: "primary" }, // disabled: { values: [true, false], defaultValue: false }, // } ``` `getSchema` omits [internal variants](/beta/getting-started/variants#internal-variants) or variants that have no values (e.g. `variants: { empty: {} }`). #### Parameters [Section titled “Parameters”](#parameters-2) `component`: a component created by `cva` (including components composed via [`composes`](#cva)) #### Returns [Section titled “Returns”](#returns-2) An object keyed by variant name. Each entry has: * `values`: a readonly array of the variant’s possible values * `defaultValue`: present only if the variant has a `defaultVariants` entry ## `cva/config` [Section titled “cva/config”](#cvaconfig) The `cva` package is a preset: [`cva`](#cva) and [`cx`](#cx) use `clsx`. `cva/config` is the same engine without a configured concatenator. ### `defineConfig` [Section titled “defineConfig”](#defineconfig) Generate `cva` and `cx` functions based on your preferred configuration. Store in a `cva.config.ts` file, and import across your project. cva.config.ts ```ts import { defineConfig } from "cva/config"; export const { cva, cx } = defineConfig(options); ``` 1. `options` * `cx` (**required**): the class name concatenator used by `cva` and `cx` * It owns the class name grammar: `cva` passes composed outputs, `base`, matched variant and compound-variant values, and `class`/`className` through verbatim, one argument each. * Custom callbacks must accept empty calls, variadic inputs, and composed component strings. * The authoring surface adopts its parameter type: `twMerge` rejects object syntax, while `clsx` keeps the full clsx-flavored `ClassValue` grammar. * See [Handling class conflicts](/beta/getting-started/installation#handling-class-conflicts) for CSS defaults, `cn`, and `tailwind-merge` examples. ### Advanced concatenators [Section titled “Advanced concatenators”](#advanced-concatenators) Use [`clsx/lite`](https://github.com/lukeed/clsx#clsxlite) only when every authored value is a string and you do not need conflict resolution. Its runtime ignores arrays, objects, and numbers even though its published types accept them. Annotate a string-only wrapper so `cva` rejects those values too: ```ts import { defineConfig } from "cva/config"; import { clsx as clsxLite } from "clsx/lite"; export const { cva, cx } = defineConfig({ cx: (...inputs: string[]) => clsxLite(...inputs), }); ``` # 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 CSS [Section titled “Tailwind CSS”](#tailwind-css) button.11ty.js ```js const { cva } = require("cva"); // ⚠️ Disclaimer: Use of Tailwind CSS is optional const button = cva({ base: "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: "py-1 px-2 text-sm", medium: "py-2 px-4 text-base", }, }, 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 source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/astro-with-tailwindcss/src/components/button.astro) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/beta/astro-with-tailwindcss?file=src/components/button.astro) or reopen this page in a Chromium-based browser. # 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 "cva"; const button = cva({ base: "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 "cva"; const greeter = cva({ base: "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. The button gallery in `src/app.tsx` reads the component schema with [`getSchema`](/beta/getting-started/tools#generate-a-react-variant-gallery), so new variant values appear without updating a second list. This example declares `defaultVariants` on the class function, so `getSchema` exposes each variant’s `defaultValue`. See [Default variants](/beta/getting-started/variants#default-variants) for when to use class-function defaults or framework prop defaults. [View source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/react-with-css-modules/src/components/button/button.tsx) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/beta/react-with-css-modules?file=src/components/button/button.tsx) or reopen this page in a Chromium-based browser. # React with Tailwind CSS > A cva button component built with React and Tailwind CSS, including compound components and class conflict resolution. The four button examples build the same React `button` component with `cva` and Tailwind CSS. Each gallery reads the component schema with [`getSchema`](/beta/getting-started/tools#generate-a-react-variant-gallery), so new variant values appear without updating a second list. The wrapper supplies defaults for the rendered button and forwards semantic `disabled` attributes. The compound example below is a separate component set. ## Basic component [Section titled “Basic component”](#basic-component) The default `cva` preset joins classes with `cx`. It keeps every matching utility in the output, so conflicting Tailwind classes remain for the stylesheet to resolve. [View source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/react-with-tailwindcss/src/components/button/button.tsx) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/beta/react-with-tailwindcss?file=src/components/button/button.tsx) or reopen this page in a Chromium-based browser. ## Handling class conflicts [Section titled “Handling class conflicts”](#handling-class-conflicts) The optional strategies below use the same variants, defaults, and gallery as the basic example. See [Handling class conflicts](/beta/getting-started/installation#handling-class-conflicts) for setup details. ### cn [Section titled “cn”](#cn) [`cn`](https://www.npmjs.com/package/cn) joins conditional classes and resolves Tailwind CSS conflicts at runtime. The configured `cx` preserves conditional inputs while the merge removes conflicting utilities. [View source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/react-with-cn/src/components/button/button.tsx) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/beta/react-with-cn?file=src/components/button/button.tsx) or reopen this page in a Chromium-based browser. ### cva/tailwindcss [Section titled “cva/tailwindcss”](#cvatailwindcss) [`cva/tailwindcss`](/beta/getting-started/installation#cvatailwindcss) marks component defaults with `base:`. Ordinary utilities override those defaults through the CSS cascade, while hover and disabled state styles stay ordinary utilities. [View source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/react-with-cva-tailwindcss/src/components/button/button.tsx) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/beta/react-with-cva-tailwindcss?file=src/components/button/button.tsx) or reopen this page in a Chromium-based browser. ### tailwind-merge [Section titled “tailwind-merge”](#tailwind-merge) Combine the `cx` included in `cva` with [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) to preserve `cva`’s full `ClassValue` grammar while resolving Tailwind CSS conflicts at runtime. [View source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/react-with-tailwind-merge/src/components/button/button.tsx) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/beta/react-with-tailwind-merge?file=src/components/button/button.tsx) or reopen this page in a Chromium-based browser. ## Compound components [Section titled “Compound components”](#compound-components) The compound example uses CSS relationships to coordinate a set of components. It stays separate from the four equivalent button examples above. [View source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/react-with-tailwindcss-compound/src/components/nav/nav.tsx) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/beta/react-with-tailwindcss-compound?file=src/components/nav/nav.tsx) or reopen this page in a Chromium-based browser. # 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 source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/svelte/src/components/button.svelte) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/beta/svelte?file=src/components/button.svelte) or reopen this page in a Chromium-based browser. # 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 source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/vue/src/components/Button.vue) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/beta/vue?file=src/components/Button.vue) or reopen this page in a Chromium-based browser. # 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 See the [“Polymorphism”](/beta/getting-started/polymorphism) documentation for further recommendations. ## 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 > 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" ``` 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. # 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 1 Content 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" ``` Extra classes may not override Extra classes are appended to the generated string. With the default `cx`, their position does not make them override conflicting CSS rules. For Tailwind CSS overrides, choose a [class conflict strategy](/beta/getting-started/installation#handling-class-conflicts). # Installation > Install cva@beta, configure Tailwind CSS IntelliSense, and handle utility style conflicts. * 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 ``` ## Join conditional classes [Section titled “Join conditional classes”](#join-conditional-classes) Use the included cx `cva` includes `cx` for joining strings, arrays, and conditional objects. You don’t need a separate `clsx` or `classnames` dependency for this. ```ts import { cx } from "cva"; const active = true; cx("button", ["rounded", { "button--active": active }]); // => "button rounded button--active" ``` `cx` joins class names; it doesn’t resolve conflicting CSS rules. See the [`cx` reference](/beta/api-reference#cx) for its inputs and [handling class conflicts](#handling-class-conflicts) for Tailwind CSS options. ## Tailwind CSS [Section titled “Tailwind CSS”](#tailwind-css) Write complete Tailwind class names Tailwind CSS scans source text and cannot detect interpolated names such as `bg-${tone}-500`. Map each variant value to a full class string instead. ```ts import { cva } from "cva"; const badge = cva({ variants: { tone: { info: "bg-blue-500 text-white", warning: "bg-amber-500 text-black", }, }, }); ``` See Tailwind’s [class detection guide](https://tailwindcss.com/docs/detecting-classes-in-source-files#dynamic-class-names) for how source scanning works. The editor settings below add autocompletion inside `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 class conflicts [Section titled “Handling class conflicts”](#handling-class-conflicts) By default, `cva` joins classes with [`clsx`](https://github.com/lukeed/clsx). It does not resolve conflicting Tailwind CSS utilities. Choose one of the approaches below: ### 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 }); ``` ### cva/tailwindcss [Section titled “cva/tailwindcss”](#cvatailwindcss) `cva/tailwindcss` is a set of custom Tailwind CSS variants, for use with or without `cva`. Import it after Tailwind CSS: ```css @import "tailwindcss"; @import "cva/tailwindcss"; ``` #### base: [Section titled “base:”](#base) The `base:` variant marks a utility as a component default, so any ordinary utility overrides it through the CSS cascade instead of runtime class merging. Prefix overridable component defaults with `base:`, including defaults selected by variants or compound variants. Write state styles as ordinary utilities: ```ts import { cva } from "cva"; export const button = cva({ base: [ "base:bg-blue-600 base:px-4 base:py-2 base:text-white", "hover:bg-blue-700", ], }); button({ className: "bg-violet-600" }); ``` In this example, `bg-violet-600` wins over `base:bg-blue-600`, and `hover:bg-blue-700` still applies on hover. `base:` utilities sit in a nested layer below ordinary utilities but above Tailwind’s `base` and `components` layers. Inspired by [Diego Haz’s `base:` idea](https://x.com/diegohaz/status/1897776774206853269) and [a related `:where()` example](https://x.com/acd02/status/1897943176863756335). #### Limitations [Section titled “Limitations”](#limitations) `base:` makes defaults overridable. It does not merge classes, so keep these rules in mind: * **Conditional defaults lose too**: any ordinary `bg-*` utility beats `base:hover:bg-*`, `base:sm:bg-*`, and `base:dark:bg-*`, even while the condition matches. Write state styles without `base:`. * **Class order still doesn’t matter**: two ordinary utilities that conflict, or two `base:` utilities that conflict, resolve by stylesheet order. * **Put `base:` before pseudo-element variants**: `base:before:bg-*` works, but `before:base:bg-*` compiles to an invalid selector and never applies. * **`!important` flips the layer order**: `base:bg-blue-600!` beats `bg-violet-600!`. For runtime conflict resolution, [`cn`](#cn) and [`tailwind-merge`](#tailwind-merge) remove earlier conflicting utilities from the class string. ### tailwind-merge [Section titled “tailwind-merge”](#tailwind-merge) Install [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) and combine it with the `cx` already exported by `cva`. This preserves strings, arrays, and conditional objects while resolving Tailwind CSS conflicts: ```sh pnpm add tailwind-merge ``` cva.config.ts ```ts import { defineConfig } from "cva/config"; import { cx as joinClasses } from "cva"; import { twMerge } from "tailwind-merge"; export const { cva, cx: cn } = defineConfig({ cx: (...inputs) => twMerge(joinClasses(...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 () => ; ``` # Skills > Use Agent Skills to build cva components and migrate between cva versions. Agent Skills give your coding agent guidance for component work and version upgrades. ## `cva-best-practices` [Section titled “cva-best-practices”](#cva-best-practices) [`cva-best-practices`](https://github.com/joe-bell/cva/tree/main/skills/cva-best-practices) condenses these docs into guidance for typed variants, defaults, composition, class conflicts, and schema-driven galleries. It checks the installed package before applying beta APIs. Install it for component work: ```sh npx skills add https://github.com/joe-bell/cva --skill cva-best-practices ``` Then give your agent a component task: > Use cva-best-practices to add size and intent variants to this button, keeping its native HTML props. The relevant guides remain the source of truth for each recommendation. Use the migration skill below when changing versions. ## `cva-migrate` [Section titled “cva-migrate”](#cva-migrate) [`cva-migrate`](https://github.com/joe-bell/cva/tree/main/skills/cva-migrate) upgrades a project between `cva` versions. Install it for repeated use, or paste the prompt into your agent to run it once: * Command ```sh npx skills add https://github.com/joe-bell/cva --skill cva-migrate ``` * Prompt ```text Run `npx skills use "https://github.com/joe-bell/cva" --skill "cva-migrate"` and follow the generated skill instructions now. Read its complete output, redirecting it to a temporary file first if necessary. Resolve relative paths from the supporting-files directory it provides. ``` Then tell the agent which version to install: > Migrate this project to `cva@1.0.0-beta.12`. The three curated routes upgrade `class-variance-authority@0.6.0` through `0.7.1` to `cva@1.0.0-beta.12`, `cva@1.0.0-beta.0` through `beta.10` to `beta.11`, and `beta.0` through `beta.11` to `beta.12`. For other routes, the skill checks every relevant [GitHub release](https://github.com/joe-bell/cva/releases), the tagged package metadata, and the published npm artifacts. It stops if those sources do not support a migration step. # 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). ## Generate a React variant gallery [Section titled “Generate a React variant gallery”](#generate-a-react-variant-gallery) The runnable [React with Tailwind CSS example](/beta/examples/react/tailwindcss#basic-component) exports its `button` class function separately from its React wrapper. The wrapper supplies `intent`, `size`, and `disabled` defaults so styling and native attributes use the same values: examples/beta/react-with-tailwindcss/src/components/button/button.tsx ```tsx import React from "react"; import { cva, type VariantProps } from "cva"; export const button = cva({ base: "font-semibold border rounded", variants: { intent: { primary: "bg-blue-500 text-white border-transparent", secondary: "bg-white text-gray-800 border-gray-400", }, size: { small: "py-1 px-2 text-sm", medium: "py-2 px-4 text-base", }, 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", class: "uppercase" }, ], }); export interface ButtonProps extends Omit, "disabled">, VariantProps {} export const Button: React.FC = ({ className, intent = "primary", size = "medium", disabled = false, ...props }) => ( `; }; ``` # 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 source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/astro-with-tailwindcss/src/components/button.astro) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/latest/astro-with-tailwindcss?file=src/components/button.astro) or reopen this page in a Chromium-based browser. # 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 source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/react-with-css-modules/src/components/button/button.tsx) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/latest/react-with-css-modules?file=src/components/button/button.tsx) or reopen this page in a Chromium-based browser. # 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 source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/react-with-tailwindcss/src/components/button/button.tsx) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/latest/react-with-tailwindcss?file=src/components/button/button.tsx) or reopen this page in a Chromium-based browser. # 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 source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/svelte/src/components/button.svelte) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/latest/svelte?file=src/components/button.svelte) or reopen this page in a Chromium-based browser. # 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 source on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/vue/src/components/Button.vue) Sorry, your browser [doesn't support](https://developer.stackblitz.com/platform/webcontainers/browser-support) this type of embed. Try [visiting the example directly](https://stackblitz.com/github/joe-bell/cva/tree/main/examples/latest/vue?file=src/components/Button.vue) or reopen this page in a Chromium-based browser. # 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) By default, `cva` joins class names but does not resolve conflicting Tailwind CSS utilities. Choose one of the approaches below: #### CSS defaults with `base:` [Section titled “CSS defaults with base:”](#css-defaults-with-base) This Tailwind CSS v4 recipe adds a `base:` variant that marks a utility as a component default, so any ordinary utility overrides it through the CSS cascade instead of runtime class merging. Add it after Tailwind’s import: ```css @import "tailwindcss"; @custom-variant base { @layer base { :where(&) { @slot; } } } ``` Prefix your defaults with `base:` and write state styles as ordinary utilities: ```ts import { cva } from "class-variance-authority"; const buttonVariants = cva( "base:bg-blue-600 base:px-4 base:py-2 base:text-white hover:bg-blue-700", ); buttonVariants({ className: "bg-violet-600" }); ``` In this example, `bg-violet-600` wins over `base:bg-blue-600`, and `hover:bg-blue-700` still applies on hover. `base:` utilities sit in a nested layer below ordinary utilities but above Tailwind’s `base` and `components` layers. Inspired by [Diego Haz’s `base:` idea](https://x.com/diegohaz/status/1897776774206853269) and [a related `:where()` example](https://x.com/acd02/status/1897943176863756335). `base:` makes defaults overridable. It does not merge classes, so keep these rules in mind: * **Conditional defaults lose too**: any ordinary `bg-*` utility beats `base:hover:bg-*`, `base:sm:bg-*`, and `base:dark:bg-*`, even while the condition matches. Write state styles without `base:`. * **Class order still doesn’t matter**: two ordinary utilities that conflict, or two `base:` utilities that conflict, resolve by stylesheet order. * **Put `base:` before pseudo-element variants**: `base:before:bg-*` works, but `before:base:bg-*` compiles to an invalid selector and never applies. * **`!important` flips the layer order**: `base:bg-blue-600!` beats `bg-violet-600!`. #### tailwind-merge [Section titled “tailwind-merge”](#tailwind-merge) If you’d rather not think about the cascade, [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) is more bulletproof: it merges classes at runtime, so the last conflicting class wins. 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 (596 B 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 "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) * ![](https://img.youtube.com/vi/kHQNK2jU_TQ/sddefault.jpg) # [Class Variance Authority (CVA) Quickstart](https://www.youtube.com/watch?v=kHQNK2jU_TQ) Coding in Public•15th September 2023 * ![](https://img.youtube.com/vi/qGQRdCg6JRQ/sddefault.jpg) # [Authoring Components with CVA + tailwindcss](https://www.youtube.com/watch?v=qGQRdCg6JRQ) React Tips with Brooks Lybrand•29th August 2023 * ![](https://img.youtube.com/vi/eXRlVpw1SIQ/sddefault.jpg) # [Creating High-Quality React Components: Best Practices for Reusability](https://www.youtube.com/watch?v=eXRlVpw1SIQ) Josh tried coding•26th February 2023 * ![](https://img.youtube.com/vi/kTSwLLFa3WM/sddefault.jpg) # [Building a design system in Next.js with Tailwind!](https://www.youtube.com/watch?v=kTSwLLFa3WM) mewtru•4th January 2023 * ![](https://img.youtube.com/vi/B6FrDu2Qbt0/sddefault.jpg) # [Large Tailwind Components — What to do About All Those Classes](https://www.youtube.com/watch?v=B6FrDu2Qbt0) frontendfyi•21st November 2022 * ![](https://img.youtube.com/vi/T-Zv73yZ_QI/sddefault.jpg) # [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).