Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/joe-bell/cva/llms.txt

Use this file to discover all available pages before exploring further.

Tailwind CSS

CVA works great with Tailwind CSS, but does not require it. You can use CVA with any utility-class library, or none at all. If you are using Tailwind, the sections below walk through IntelliSense setup, conflict resolution, and the Prettier plugin.

IntelliSense

Tailwind’s language server supports a classFunctions option that enables autocompletion and hover previews inside any function you name — including cva and cx.
1

Install the extension

Install the Tailwind CSS IntelliSense extension from the VS Code Marketplace.
2

Add classFunctions to settings.json

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

Handling style conflicts

Tailwind applies styles based on the order classes appear in your stylesheet, not the order they appear in the class attribute. This means two conflicting utilities — for example p-2 and p-4 — will not resolve predictably when merged at runtime. CVA’s variant API is designed to minimise these situations, but they can still occur when a consumer passes extra classes via the class or className prop. The tailwind-merge package resolves these conflicts by keeping only the last conflicting utility in the string.

Global setup with defineConfig

The cleanest approach is to configure tailwind-merge once using defineConfig and re-export the resulting cva, cx, and compose from a single module. Every component that imports from that module gets conflict resolution for free.
lib/utils.ts
import { defineConfig } from "cva";
import { twMerge } from "tailwind-merge";

export const { cva, cx, compose } = defineConfig({
  hooks: {
    onComplete: (className) => twMerge(className),
  },
});
Then import from your local module instead of cva directly:
components/button.ts
import { cva, type VariantProps } from "../lib/utils";

export const button = cva({
  base: ["font-semibold", "border", "rounded"],
  variants: {
    intent: {
      primary: ["bg-blue-500", "text-white", "border-transparent"],
      secondary: ["bg-white", "text-gray-800", "border-gray-400"],
    },
  },
  defaultVariants: { intent: "primary" },
});

export type ButtonProps = VariantProps<typeof button>;

Per-component setup

If you prefer to apply tailwind-merge only to specific components, wrap the CVA call manually:
components/button.ts
import { cva, type VariantProps } from "class-variance-authority";
import { twMerge } from "tailwind-merge";

const buttonVariants = cva(["font-semibold", "border", "rounded"], {
  variants: {
    intent: {
      primary: ["bg-blue-500", "text-white", "border-transparent"],
    },
  },
  defaultVariants: {
    intent: "primary",
  },
});

export interface ButtonProps extends VariantProps<typeof buttonVariants> {}

export const button = (variants: ButtonProps) =>
  twMerge(buttonVariants(variants));
The global defineConfig approach is recommended for most projects. It keeps conflict resolution in one place and works transparently with cx and compose as well.

Prettier plugin

prettier-plugin-tailwindcss automatically sorts Tailwind classes. It supports the same classFunctions option as the language server, so you can configure it to sort classes inside cva and cx calls:
.prettierrc
{
  "plugins": ["prettier-plugin-tailwindcss"],
  "tailwindFunctions": ["cva", "cx"]
}
The Prettier plugin option is named tailwindFunctions, not classFunctions. Check the plugin documentation for the current option name.

Build docs developers (and LLMs) love