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` 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) *  # [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)