This is the abridged 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` builds type-safe, variant-driven class names for any styling approach, with first-class Tailwind CSS support. 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) ## 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 * [**clb**](https://github.com/crswll/clb) ([Bill Criswell](https://github.com/crswll))\ This project originally started out with the intention of merging into the wonderful [`clb`](https://github.com/crswll/clb) library, but after some discussion with Bill, we felt it was best to go down the route of a separate project.\ 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` # 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. - 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. 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 ``` ## 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`. # 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) To kick things off, let’s build a “basic” `button` component, using `cva` to handle our variant’s classes 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)