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` 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) 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. ## 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` # 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) 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. ## 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 > API reference for cva, cva/config, and cva/tools. ## `cva` [Section titled “cva”](#cva) Builds a `cva` component ```ts import { cva } from "cva"; const component = cva(options); ``` `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` and `hooks` you gave [`defineConfig`](#defineconfig), which stay 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"; const className = cx(classes); ``` ### Parameters [Section titled “Parameters”](#parameters-1) * `classes`: 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/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`, `cx`, and `compose` * 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 [Merging classes](/beta/getting-started/installation#merging-classes) for `cn` and `tailwind-merge` examples. * `hooks` * `onComplete` and `"cx:done"` are deprecated. Each receives the concatenated string and returns a processed string. Use `cx` instead. When both hooks are set, `"cx:done"` takes precedence. `defineConfig` also returns a deprecated `compose` function. Use the `composes` property on `cva` instead. See [What’s new?](/beta/getting-started/whats-new#1-compose--composes). ### 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 on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/astro-with-tailwindcss/src/components/button.astro) # BEM > Use cva to apply BEM class names instead of utility classes. This example applies BEM class names with `cva` instead of utility classes. styles.css ```css .button { /* */ } .button--primary { /* */ } .button--secondary { /* */ } .button--small { /* */ } .button--medium { /* */ } .button--primary-medium { /* */ } ``` ```ts import { cva } from "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. [View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/react-with-css-modules/src/components/button/button.tsx) # React with Tailwind CSS > A cva button component built with React and Tailwind CSS, including compound components and class conflict resolution. These examples build a React `button` component with `cva` and Tailwind CSS. They cover compound components and conflicting Tailwind classes. ## Basic component [Section titled “Basic component”](#basic-component) [View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/react-with-tailwindcss/src/components/button/button.tsx) ## Compound components [Section titled “Compound components”](#compound-components) [View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/react-with-tailwindcss-compound/src/components/nav/nav.tsx) ## Merging classes [Section titled “Merging classes”](#merging-classes) Both examples resolve conflicting Tailwind CSS classes and conditional inputs. See [Merging classes](/beta/getting-started/installation#merging-classes) for the configurations. ### cn [Section titled “cn”](#cn) [`cn`](https://www.npmjs.com/package/cn) joins conditional classes and resolves Tailwind CSS conflicts in one function. [View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/react-with-cn/src/cva.config.ts) ### tailwind-merge [Section titled “tailwind-merge”](#tailwind-merge) [`clsx`](https://github.com/lukeed/clsx) with [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) preserves `cva`’s full `ClassValue` grammar while resolving Tailwind CSS conflicts. [View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/beta/react-with-tailwind-merge/src/cva.config.ts) # 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/beta/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/beta/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 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" ``` Caution Pass components to `composes` as an inline array literal, or one marked `as const`. A pre-declared, mutable array variable (`const list = [a, b]`) loses the tuple inference `composes` relies on, which can silently widen or drop variant types. `cva` reads the array once, when you create the component, so pushing another component onto it afterwards has no effect. Create the parent again with the full list instead. # Compound Components > Build compound component sets, such as Accordion.Item, with cva and CSS. For larger, more complex components, you may end up wanting to create a set of composable components that work together: “Compound Components” e.g. `` instead of `` ```tsx import * as Accordion from "./Accordion"; function Example() { return ( Section 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" ``` # Installation > Install cva@beta, configure Tailwind CSS IntelliSense, and merge conflicting utility classes. * pnpm ```sh pnpm i cva@beta ``` * npm ```sh npm i cva@beta ``` * yarn ```sh yarn add cva@beta ``` * bun ```sh bun add cva@beta ``` * deno ```sh deno add cva@beta ``` ## Tailwind CSS [Section titled “Tailwind CSS”](#tailwind-css) If you’re a Tailwind user, here are some additional (optional) steps to get the most out of `cva`: ### IntelliSense [Section titled “IntelliSense”](#intellisense) You can enable autocompletion inside `cva` using the steps below: * Visual Studio Code 1. [Install the “Tailwind CSS IntelliSense” Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) 2. Add the following to your [`.vscode/settings.json`](https://code.visualstudio.com/docs/getstarted/settings): .vscode/settings.json ```json { "tailwindCSS.classFunctions": ["cva", "cx"], } ``` * Zed Add the following to your [`.zed/settings.json`](https://zed.dev/docs/configuring-zed#settings-files): .zed/settings.json ```json { "lsp": { "tailwindcss-language-server": { "settings": { "classFunctions": ["cva", "cx"], } } } } ``` * Neovim 1. [Install the extension](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#tailwindcss) 2. Add the following configuration: ```lua require 'lspconfig'.tailwindcss.setup({ settings = { tailwindCSS = { classFunctions = { "cva", "cx" }, }, }, }) ``` * WebStorm 1. Check the version. Available for [WebStorm 2023.1](https://www.jetbrains.com/webstorm/whatsnew/#version-2023-1-tailwind-css-configuration) and later 2. Open the settings. Go to [Languages and Frameworks | Style Sheets | Tailwind CSS](https://www.jetbrains.com/help/webstorm/tailwind-css.html#ws_css_tailwind_configuration) 3. Add the following to your tailwind configuration ```json { "classFunctions": ["cva", "cx"] } ``` ## Merging classes [Section titled “Merging classes”](#merging-classes) By default, `cva` uses [`clsx`](https://github.com/lukeed/clsx) for conditional class joining. It does not resolve conflicting Tailwind CSS utilities. Both configurations accept conditional objects and arrays. ### cn [Section titled “cn”](#cn) Install the [`cn`](https://www.npmjs.com/package/cn) package to join conditional classes and resolve Tailwind CSS conflicts in one function. The [shadcn CLI](https://ui.shadcn.com/docs/registry/github) installs `cva@beta` with `cn` and writes `cva.config.ts` to your project root: ```sh pnpm dlx shadcn@latest add joe-bell/cva/cn ``` To set it up by hand instead, add `cn` and export the configured functions from `cva.config.ts`: ```sh pnpm add cn ``` cva.config.ts ```ts import { defineConfig } from "cva/config"; import { cn as merge } from "cn"; export const { cva, cx: cn } = defineConfig({ cx: merge }); ``` ### tailwind-merge [Section titled “tailwind-merge”](#tailwind-merge) Install [`clsx`](https://github.com/lukeed/clsx) with [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) to preserve `cva`’s full `ClassValue` grammar while resolving Tailwind CSS conflicts: ```sh pnpm add clsx tailwind-merge ``` cva.config.ts ```ts import { defineConfig } from "cva/config"; import { clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export const { cva, cx: cn } = defineConfig({ cx: (...inputs) => twMerge(clsx(inputs)), }); ``` Bare `twMerge` Use bare `twMerge` when your authored values are strings or arrays. TypeScript rejects object syntax, but JavaScript has no compile-time protection. Exported configurations can expose tailwind-merge’s `ClassNameValue` in your library declarations. ```ts import { defineConfig } from "cva/config"; import { twMerge } from "tailwind-merge"; export const { cva, cx: cn } = defineConfig({ cx: twMerge }); ``` ### Use either configuration [Section titled “Use either configuration”](#use-either-configuration) Use the configured functions throughout your project: components/button.ts ```ts import { cn, cva } from "../cva.config"; export const button = cva({ base: "font-semibold bg-gray-200 border rounded", variants: { intent: { primary: "bg-blue-500 text-white border-transparent hover:bg-blue-600", secondary: "bg-white text-gray-800 border-gray-400 hover:bg-gray-100", }, }, defaultVariants: { intent: "primary", }, }); ``` Both configurations produce the same output: ```ts button(); // => "font-semibold border rounded bg-blue-500 text-white border-transparent hover:bg-blue-600" cn("bg-gray-200", { "bg-blue-500": true }); // => "bg-blue-500" ``` For string-only authoring without conflict resolution, see [advanced concatenators](/beta/api-reference#advanced-concatenators) for `clsx/lite`. # Polymorphism > Apply cva classes to any HTML element, or roll your own render prop with Base UI's useRender. `cva` components are polymorphic (and framework-agnostic) by default: apply the class to your preferred HTML element… ```tsx import { button } from "./components/button"; export default () => ( Sign up ); ``` ## Alternative approaches [Section titled “Alternative approaches”](#alternative-approaches) ### React [Section titled “React”](#react) If you’d prefer to use a React-based API, `cva` strongly recommends using [Base UI’s `useRender` hook](https://base-ui.com/react/utils/use-render) to roll your own `render` prop. ```tsx "use client"; import { cva, type VariantProps } from "cva"; import { useRender } from "@base-ui/react/use-render"; import { mergeProps } from "@base-ui/react/merge-props"; const _BUTTON_DEFAULT_TAG = "button" satisfies React.ElementType; export interface ButtonProps extends useRender.ComponentProps, VariantProps {} const button = cva({ base: "button", variants: { intent: { primary: "bg-blue-500 hover:bg-blue-600 border-transparent text-white", secondary: "border-gray-400 bg-white text-gray-800 hover:bg-gray-100", }, }, }); export function Button({ render, intent = "primary", className, ...props }: ButtonProps) { const defaultProps: useRender.ElementProps = { className: button({ intent, className }), // Consider data-attributes for debugging purposes ["data-button" as string]: "", ["data-intent" as string]: intent, }; return useRender({ defaultTagName: _BUTTON_DEFAULT_TAG, render, props: mergeProps(defaultProps, props), }); } ``` #### Usage [Section titled “Usage”](#usage) ```tsx import { Button } from "./components/button"; // Renders: // // Contact // export default () => ; ``` # Tools > Read a component's variant names, values, and defaults with getSchema from cva/tools. The `cva/tools` entry point holds the helpers that read a component’s variants back out at runtime. It’s a separate entry point, so an ESM bundler drops it from a build that never imports it. ## `getSchema` [Section titled “getSchema”](#getschema) Re-declaring variants for a Storybook story or prop table creates a second copy to keep in sync by hand. `getSchema` reads them from the component instead, so you declare each variant once: ```ts import { cva } from "cva"; import { getSchema } from "cva/tools"; const button = cva({ base: "button", variants: { intent: { primary: "button--primary", secondary: "button--secondary" }, size: { small: "button--small", large: "button--large" }, }, defaultVariants: { intent: "primary", size: "small" }, }); getSchema(button); // => { // intent: { values: ["primary", "secondary"], defaultValue: "primary" }, // size: { values: ["small", "large"], defaultValue: "small" }, // } ``` The schema is fully typed: `values` narrows to the variant’s literal values, and `defaultValue` only appears when the component declares one. Hover the result in your editor and you see the same shape the runtime returns: ```ts import { cva } from "cva"; import { getSchema } from "cva/tools"; const badge = cva({ base: "badge", variants: { tone: { info: "badge--info", warning: "badge--warning" }, round: { true: "badge--round", false: "badge--square" }, weight: { 400: "badge--regular", 700: "badge--bold" }, }, defaultVariants: { tone: "info" }, }); const schema = getSchema(badge); // ^ { // tone: { // values: readonly ("info" | "warning")[]; // defaultValue: "info"; // }; // round: { values: readonly boolean[] }; // weight: { values: readonly (400 | 700)[] }; // } ``` Boolean and numeric variant keys come back as booleans and numbers, matching the props the component accepts rather than the object keys they were written as. [Internal variants](/beta/getting-started/variants#internal-variants) are omitted. For the full signature, see the [API reference](/beta/api-reference#getschema). # TypeScript > Extract variant types with VariantProps and require specific variants with TypeScript utility types. ## Extracting variant types [Section titled “Extracting variant types”](#extracting-variant-types) `cva` offers the `VariantProps` helper to extract variant types. `VariantProps` omits variant names prefixed with `_`; see [Internal variants](/beta/getting-started/variants#internal-variants). components/button.ts ```ts import type { VariantProps } from "cva"; import { cva, cx } from "cva"; /** * Button */ export type ButtonProps = VariantProps; export const button = cva(/* … */); ``` ## Required variants [Section titled “Required variants”](#required-variants) To keep the API small and unopinionated, `cva` **doesn’t** offer a built-in solution for setting required variants. Instead, we recommend using TypeScript’s [Utility Types](https://www.typescriptlang.org/docs/handbook/utility-types.html): components/button.ts ```ts import { cva, type VariantProps } from "cva"; export type ButtonVariantProps = VariantProps; export const buttonVariants = cva({ base: "…", variants: { optional: { a: "…", b: "…" }, required: { a: "…", b: "…" }, }, }); /** * Button */ export interface ButtonProps extends Omit, Required> {} export const button = (props: ButtonProps) => buttonVariants(props); // ❌ TypeScript Error: // Argument of type "{}": is not assignable to parameter of type "ButtonProps". // Property "required" is missing in type "{}" but required in type // "ButtonProps". button({}); // ✅ button({ required: "a" }); ``` # Variants > Create variants, compound variants, and disable variants with cva@beta. ## Creating variants [Section titled “Creating variants”](#creating-variants) Note Although `cva` is a [**tiny**](https://bundlephobia.com/package/cva) library, it’s best to use in an environment with server-side rendering (SSR) or static-site generation (SSG): your user probably doesn’t need this JavaScript, especially for static components. To kick things off, let’s build a “basic” `button` component, using `cva` to handle our variant’s classes Note Use of Tailwind CSS is optional components/button.ts ```ts import { cva } from "cva"; const button = cva({ base: "rounded border font-semibold", // **or** // base: ["font-semibold", "border rounded"], variants: { intent: { primary: "border-transparent bg-blue-500 text-white hover:bg-blue-600", // **or** // primary: [ // "bg-blue-500 text-white border-transparent", // "hover:bg-blue-600", // ], secondary: "border-gray-400 bg-white text-gray-800 hover:bg-gray-100", }, size: { small: "px-2 py-1 text-sm", medium: "px-4 py-2 text-base", }, }, compoundVariants: [ { intent: "primary", size: "medium", class: "uppercase", // **or** if you're a React.js user, `className` may feel more consistent: // className: "uppercase" }, ], defaultVariants: { intent: "primary", size: "medium", }, }); button(); // => "rounded border font-semibold border-transparent bg-blue-500 text-white hover:bg-blue-600 px-4 py-2 text-base uppercase" button({ intent: "secondary", size: "small" }); // => "rounded border font-semibold border-gray-400 bg-white text-gray-800 hover:bg-gray-100 px-2 py-1 text-sm" ``` ## Internal variants [Section titled “Internal variants”](#internal-variants) A variant name prefixed with `_` is treated as internal. Internal variants remain available to the component. You can set them via `defaultVariants` and match them in `compoundVariants`. Both [`VariantProps`](/beta/getting-started/typescript#extracting-variant-types) and [`getSchema`](/beta/api-reference#getschema) omit them. ```ts import { cva, type VariantProps } from "cva"; const button = cva({ base: "button", variants: { _intent: { primary: "button--primary", secondary: "button--secondary" }, size: { small: "button--small", medium: "button--medium" }, }, defaultVariants: { _intent: "primary", size: "medium", }, }); // `_intent` is omitted from the public props. type ButtonProps = VariantProps; // => { size?: "small" | "medium" | undefined } // The component still accepts `_intent` directly. button({ _intent: "secondary", size: "small" }); // => "button button--secondary button--small" ``` In a React component, extend `VariantProps` for the public props, then set the internal variant yourself: ```tsx interface Props extends React.ButtonHTMLAttributes, VariantProps { active?: boolean; } // `_intent` never appears in `Props`, so a consumer can't set it. function Button({ active, size, className, ...props }: Props) { return ( `; }; ``` # Astro > A cva button component built with Astro and Tailwind CSS. This example builds a `button` component with `cva` in an Astro project. ## Tailwind CSS [Section titled “Tailwind CSS”](#tailwind-css) [View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/astro-with-tailwindcss/src/components/button.astro) # BEM > Use cva to apply BEM class names instead of utility classes. This example applies BEM class names with `cva` instead of utility classes. styles.css ```css .button { /* */ } .button--primary { /* */ } .button--secondary { /* */ } .button--small { /* */ } .button--medium { /* */ } .button--primary-medium { /* */ } ``` ```ts import { cva } from "class-variance-authority"; const button = cva("button", { variants: { intent: { primary: "button--primary", secondary: "button--secondary", }, size: { small: "button--small", medium: "button--medium", }, }, compoundVariants: [ { intent: "primary", size: "medium", class: "button--primary-medium" }, ], defaultVariants: { intent: "primary", size: "medium", }, }); button(); // => "button button--primary button--medium button--primary-medium" button({ intent: "secondary", size: "small" }); // => "button button--secondary button--small" ``` # Other Use Cases > Use cva to manage variant-driven strings beyond class names, such as dynamic text content. Although primarily designed for handling class names, at its core `cva` is a fancy way of managing a string… ## Dynamic text content [Section titled “Dynamic text content”](#dynamic-text-content) ```ts import { cva } from "class-variance-authority"; const greeter = cva("Good morning!", { variants: { isLoggedIn: { true: "Here's a secret only logged in users can see", false: "Log in to find out more…", }, }, defaultVariants: { isLoggedIn: false, }, }); greeter(); // => "Good morning! Log in to find out more…" greeter({ isLoggedIn: true }); // => "Good morning! Here's a secret only logged in users can see" ``` # React with CSS Modules > A cva button component styled with CSS Modules in React. This example builds a `button` component with `cva`, styled with CSS Modules, in a React project. [View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/react-with-css-modules/src/components/button/button.tsx) # React with Tailwind CSS > A cva button component built with React and Tailwind CSS. This example builds a `button` component with `cva` and Tailwind CSS in a React project. [View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/react-with-tailwindcss/src/components/button/button.tsx) # Svelte > A cva button component built with Svelte. This example builds a `button` component with `cva` in a Svelte project. Open the sandbox below to explore the full source. [View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/svelte/src/components/button.svelte) # Vue > A cva button component built with Vue. This example builds a `Button` component with `cva` in a Vue project. Open the sandbox below to explore the full source. [View on GitHub ↗](https://github.com/joe-bell/cva/tree/main/examples/latest/vue/src/components/Button.vue) # FAQs > Answers to common questions about cva's API design, including responsive variants and styled-component APIs. ## Why don’t you provide a `styled` API? [Section titled “Why don’t you provide a styled API?”](#why-dont-you-provide-a-styled-api) Long story short: it’s unnecessary. `cva` encourages you to think of components as traditional CSS classes: * Less JavaScript is better * They’re framework agnostic; truly reusable * Polymorphism is free: apply the class to your preferred HTML element * Less opinionated; you’re free to build components with `cva` however you’d like ## How can I create [responsive variants like Stitches.js](https://stitches.dev/docs/responsive-styles#responsive-variants)? [Section titled “How can I create responsive variants like Stitches.js?”](#how-can-i-create-responsive-variants-like-stitchesjs) You can’t. `cva` doesn’t know about how you choose to apply CSS classes, and it doesn’t want to. We recommend either: * Showing/hiding elements with different variants, based on your preferred breakpoint. Example: With Tailwind ```tsx export const Example = () => ( <>
); ``` * Create a bespoke variant that changes based on the breakpoint. *e.g. `button({ intent: "primaryUntilMd" })`* This is something I’ve been thinking about since the project’s inception, and I’ve gone back and forth many times on the idea of building it. It’s a large undertaking and brings all the complexity of supporting many different build tools and frameworks. In my experience, “responsive variants” are typically rare, and hiding/showing different elements is usually good enough to get by. To be frank, I’m probably not going to build/maintain a solution unless someone periodically gives me a thick wad of cash to do so, and even then I’d probably rather spend my free time living my life. # Composing Components > Extend and concatenate cva components with cx. Tip `cva@beta` has first-class composition via the `composes` property. See [Composing components](/beta/getting-started/composing-components). While `cva` doesn’t offer a built-in method for composing components together, it does offer the tools to *extend* components on your own terms… For example; two `cva` components, concatenated together with `cx`: components/card.ts ```ts import type { VariantProps } from "class-variance-authority"; import { cva, cx } from "class-variance-authority"; /** * Box */ export type BoxProps = VariantProps; export const box = cva(["box", "box-border"], { variants: { margin: { 0: "m-0", 2: "m-2", 4: "m-4", 8: "m-8" }, padding: { 0: "p-0", 2: "p-2", 4: "p-4", 8: "p-8" }, }, defaultVariants: { margin: 0, padding: 0, }, }); /** * Card */ type CardBaseProps = VariantProps; const cardBase = cva(["card", "border-solid", "border-slate-300", "rounded"], { variants: { shadow: { md: "drop-shadow-md", lg: "drop-shadow-lg", xl: "drop-shadow-xl", }, }, }); export interface CardProps extends BoxProps, CardBaseProps {} export const card = ({ margin, padding, shadow }: CardProps = {}) => cx(box({ margin, padding }), cardBase({ shadow })); ``` # Extending Components > Pass extra classes to a cva component with the class or className prop. All `cva` components provide an optional `class` **or** `className` prop, which can be used to pass additional classes to the component. components/button.ts ```ts import { cva } from "class-variance-authority"; const button = cva("font-semibold"); button({ class: "m-4" }); // => "font-semibold m-4" button({ className: "m-4" }); // => "font-semibold m-4" ``` # Installation > Install class-variance-authority and configure Tailwind CSS IntelliSense and style-conflict handling. * pnpm ```sh pnpm i class-variance-authority ``` * npm ```sh npm i class-variance-authority ``` * yarn ```sh yarn add class-variance-authority ``` * bun ```sh bun add class-variance-authority ``` * deno ```sh deno add class-variance-authority ``` Do I have to write such a long package name? Unfortunately, for a little bit longer, yes. Originally, the plan was to publish the package as `cva`, but someone had already registered that name on npm and [marked it as a “placeholder”](https://www.npmjs.com/package/cva). On 2022/02/16, GitHub transferred NPM ownership of `cva` to [Joe Bell](https://joebell.studio). This shorter name will be used from v1 onwards. In the meantime, you can always alias the package for your convenience… 1. Alias the package with [`npm install`](https://docs.npmjs.com/cli/v6/commands/npm-install) ```sh npm i cva@npm:class-variance-authority ``` 2. Then import like so: ```ts import { cva } from "cva"; // … ``` ## Tailwind CSS [Section titled “Tailwind CSS”](#tailwind-css) If you’re a Tailwind user, here are some additional (optional) steps to get the most out of `cva`: ### IntelliSense [Section titled “IntelliSense”](#intellisense) You can enable autocompletion inside `cva` using the steps below: * Visual Studio Code 1. [Install the “Tailwind CSS IntelliSense” Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) 2. Add the following to your [`settings.json`](https://code.visualstudio.com/docs/getstarted/settings): ```json { "tailwindCSS.classFunctions": ["cva", "cx"] } ``` * Zed Add the following to your [`.zed/settings.json`](https://zed.dev/docs/configuring-zed#settings-files): ```json { "lsp": { "tailwindcss-language-server": { "settings": { "classFunctions": ["cva", "cx"], } } } } ``` * Neovim 1. [Install the extension](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#tailwindcss) 2. Add the following configuration: ```lua require 'lspconfig'.tailwindcss.setup({ settings = { tailwindCSS = { classFunctions = { "cva", "cx" }, }, }, }) ``` * WebStorm 1. Check the version. Available for [WebStorm 2023.1](https://www.jetbrains.com/webstorm/whatsnew/#version-2023-1-tailwind-css-configuration) and later 2. Open the settings. Go to [Languages and Frameworks | Style Sheets | Tailwind CSS](https://www.jetbrains.com/help/webstorm/tailwind-css.html#ws_css_tailwind_configuration) 3. Add the following to your tailwind configuration ```json { "classFunctions": ["cva", "cx"] } ``` ### Handling style conflicts [Section titled “Handling style conflicts”](#handling-style-conflicts) Although `cva`’s API is designed to help you avoid styling conflicts, there’s still a small margin of error. If you’re keen to lift that burden altogether, check out the wonderful [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) package. For bulletproof components, wrap your `cva` component with `twMerge`. Example with tailwind-merge ```ts import { cva, type VariantProps } from "class-variance-authority"; import { twMerge } from "tailwind-merge"; const buttonVariants = cva(["your", "base", "classes"], { variants: { intent: { primary: ["your", "primary", "classes"], }, }, defaultVariants: { intent: "primary", }, }); export interface ButtonVariants extends VariantProps {} export const button = (variants: ButtonVariants) => twMerge(buttonVariants(variants)); ``` # TypeScript > Extract variant types with VariantProps and require specific variants with TypeScript utility types. ## Extracting variant types [Section titled “Extracting variant types”](#extracting-variant-types) `cva` offers the `VariantProps` helper to extract variant types components/button.ts ```ts import type { VariantProps } from "class-variance-authority"; import { cva, cx } from "class-variance-authority"; /** * Button */ export type ButtonProps = VariantProps; export const button = cva(/* … */); ``` ## Required variants [Section titled “Required variants”](#required-variants) To keep the API small and unopinionated, `cva` **doesn’t** offer a built-in solution for setting required variants. Instead, we recommend using TypeScript’s [Utility Types](https://www.typescriptlang.org/docs/handbook/utility-types.html): components/button.ts ```ts import { cva, type VariantProps } from "class-variance-authority"; export type ButtonVariantProps = VariantProps; export const buttonVariants = cva("…", { variants: { optional: { a: "…", b: "…" }, required: { a: "…", b: "…" }, }, }); /** * Button */ export interface ButtonProps extends Omit, Required> {} export const button = (props: ButtonProps) => buttonVariants(props); // ❌ TypeScript Error: // Argument of type "{}": is not assignable to parameter of type "ButtonProps". // Property "required" is missing in type "{}" but required in type // "ButtonProps". button({}); // ✅ button({ required: "a" }); ``` # Variants > Create variants, compound variants, and default variants with cva. ## Creating variants [Section titled “Creating variants”](#creating-variants) Note Although `cva` is a [**tiny**](https://bundlephobia.com/package/class-variance-authority) library, it’s best to use in an environment with server-side rendering (SSR) or static-site generation (SSG): your user probably doesn’t need this JavaScript, especially for static components. To kick things off, let’s build a “basic” `button` component, using `cva` to handle our variant’s classes Note Use of Tailwind CSS is optional components/button.ts ```ts import { cva } from "class-variance-authority"; const button = cva(["font-semibold", "border", "rounded"], { variants: { intent: { primary: ["bg-blue-500", "text-white", "border-transparent"], // **or** // primary: "bg-blue-500 text-white border-transparent hover:bg-blue-600", secondary: ["bg-white", "text-gray-800", "border-gray-400"], }, size: { small: ["text-sm", "py-1", "px-2"], medium: ["text-base", "py-2", "px-4"], }, // `boolean` variants are also supported! disabled: { false: null, true: ["opacity-50", "cursor-not-allowed"], }, }, compoundVariants: [ { intent: "primary", disabled: false, class: "hover:bg-blue-600", }, { intent: "secondary", disabled: false, class: "hover:bg-gray-100", }, { intent: "primary", size: "medium", // **or** if you're a React.js user, `className` may feel more consistent: // className: "uppercase" class: "uppercase", }, ], defaultVariants: { intent: "primary", size: "medium", disabled: false, }, }); button(); // => "font-semibold border rounded bg-blue-500 text-white border-transparent text-base py-2 px-4 hover:bg-blue-600 uppercase" button({ disabled: true }); // => "font-semibold border rounded bg-blue-500 text-white border-transparent text-base py-2 px-4 opacity-50 cursor-not-allowed uppercase" button({ intent: "secondary", size: "small" }); // => "font-semibold border rounded bg-white text-gray-800 border-gray-400 text-sm py-1 px-2 hover:bg-gray-100" ``` ## Compound variants [Section titled “Compound variants”](#compound-variants) Variants that apply when multiple other variant conditions are met. components/button.ts ```ts import { cva } from "class-variance-authority"; const button = cva("…", { variants: { intent: { primary: "…", secondary: "…" }, size: { small: "…", medium: "…" }, }, compoundVariants: [ // Applied via: // `button({ intent: "primary", size: "medium" })` { intent: "primary", size: "medium", class: "…", }, ], }); ``` ### Targeting multiple variant conditions [Section titled “Targeting multiple variant conditions”](#targeting-multiple-variant-conditions) components/button.ts ```ts import { cva } from "class-variance-authority"; const button = cva("…", { variants: { intent: { primary: "…", secondary: "…" }, size: { small: "…", medium: "…" }, }, compoundVariants: [ // Applied via: // `button({ intent: "primary", size: "medium" })` // or // `button({ intent: "secondary", size: "medium" })` { intent: ["primary", "secondary"], size: "medium", class: "…", }, ], }); ``` # Tutorials > Community videos, podcasts, and articles about cva. ## YouTube [Section titled “YouTube”](#youtube) * ![](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).