Skip to content

Installation

This content is for Beta. Switch to the latest version for up-to-date documentation.

Terminal window
pnpm i cva@beta

If you’re a Tailwind user, here are some additional (optional) steps to get the most out of cva:

You can enable autocompletion inside cva using the steps below:

  1. Install the “Tailwind CSS IntelliSense” Visual Studio Code extension

  2. Add the following to your .vscode/settings.json:

    .vscode/settings.json
    {
    "tailwindCSS.classFunctions": ["cva", "cx"],
    }

If you want to merge Tailwind CSS classes without conflicts, swap cva’s concatenator via defineConfig from cva/core: pass tailwind-merge, a drop-in like cnfast, or your own function as the cx option.

Your concatenator owns the class name grammar entirely: cva assembles the authored values (base, matched variants, class/className) and passes them through verbatim, one argument each. The authoring surface adopts your concatenator’s input type automatically, so your variants are checked against exactly what it supports.

Example with tailwind-merge
cva.config.ts
import { defineConfig } from "cva/core";
import { twMerge } from "tailwind-merge";
export const { cva, cx } = defineConfig({
cx: twMerge,
});

twMerge accepts strings and arrays but not objects, so this cva’s variants are typechecked against tailwind-merge’s own input type: object syntax becomes a compile error instead of a silently dropped class. Prefer to keep clsx’s object syntax available? Compose the two instead: cx: (...inputs) => twMerge(clsx(inputs)).

components/button.ts
import { cx, cva } from "../cva.config";
export const button = cva({
// 1. `twMerge` strips out `bg-gray-200`…
base: "font-semibold bg-gray-200 border rounded",
variants: {
intent: {
// 2. …as variant `bg-*` values take precedence
primary: "bg-blue-500 text-white border-transparent hover:bg-blue-600",
secondary: "bg-white text-gray-800 border-gray-400 hover:bg-gray-100",
},
},
defaultVariants: {
intent: "primary",
},
});
button();
// => "font-semibold border rounded bg-blue-500 text-white border-transparent hover:bg-blue-600"
cx("bg-gray-200", "bg-blue-500");
// => "bg-blue-500"
Example with cnfast

cnfast’s cn combines clsx and tailwind-merge behavior in one function, and its input type matches cva’s default contract, so it drops straight in with objects and arrays intact.

cva.config.ts
import { defineConfig } from "cva/core";
import { cn } from "cnfast";
export const { cva, cx } = defineConfig({
cx: cn,
});
cx("bg-gray-200", { "bg-blue-500": true });
// => "bg-blue-500"
Example with clsx/lite

If your components are authored with plain strings only and you don’t need conflict resolution, clsx/lite is the smallest option. Heads up: its runtime silently ignores anything that isn’t a string (arrays, objects, numbers), even though its published types claim otherwise, so keep your variant values as strings.

cva.config.ts
import { defineConfig } from "cva/core";
import { clsx } from "clsx/lite";
export const { cva, cx } = defineConfig({
cx: clsx,
});
cx("foo", "bar", "baz");
// => "foo bar baz"