---
title: "Installation"
description: "Install cva@beta, configure Tailwind CSS IntelliSense, and handle utility style conflicts."
url: "https://cva.style/beta/getting-started/installation/"
---

* 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
  ```

## Join conditional classes

Use the included cx

`cva` includes `cx` for joining strings, arrays, and conditional objects. You don’t need a separate `clsx` or `classnames` dependency for this.

```ts
import { cx } from "cva";


const active = true;
cx("button", ["rounded", { "button--active": active }]);
// => "button rounded button--active"
```

`cx` joins class names; it doesn’t resolve conflicting CSS rules. See the [`cx` reference](https://cva.style/beta/api-reference#cx) for its inputs and [handling class conflicts](#handling-class-conflicts) for Tailwind CSS options.

## Tailwind CSS

Write complete Tailwind class names

Tailwind CSS scans source text and cannot detect interpolated names such as `bg-${tone}-500`. Map each variant value to a full class string instead.

```ts
import { cva } from "cva";


const badge = cva({
  variants: {
    tone: {
      info: "bg-blue-500 text-white",
      warning: "bg-amber-500 text-black",
    },
  },
});
```

See Tailwind’s [class detection guide](https://tailwindcss.com/docs/detecting-classes-in-source-files#dynamic-class-names) for how source scanning works. The editor settings below add autocompletion inside `cva`.

### 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"]
     }
     ```

## Handling class conflicts

By default, `cva` joins classes with [`clsx`](https://github.com/lukeed/clsx). It does not resolve conflicting Tailwind CSS utilities. Choose one of the approaches below:

### 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 });
```

### cva/tailwindcss

`cva/tailwindcss` is a set of custom Tailwind CSS variants, for use with or without `cva`. Import it after Tailwind CSS:

```css
@import "tailwindcss";
@import "cva/tailwindcss";
```

#### base:

The `base:` variant marks a utility as a component default, so any ordinary utility overrides it through the CSS cascade instead of runtime class merging.

Prefix overridable component defaults with `base:`, including defaults selected by variants or compound variants. Write state styles as ordinary utilities:

```ts
import { cva } from "cva";


export const button = cva({
  base: [
    "base:bg-blue-600 base:px-4 base:py-2 base:text-white",
    "hover:bg-blue-700",
  ],
});


button({ className: "bg-violet-600" });
```

In this example, `bg-violet-600` wins over `base:bg-blue-600`, and `hover:bg-blue-700` still applies on hover.

`base:` utilities sit in a nested layer below ordinary utilities but above Tailwind’s `base` and `components` layers. Inspired by [Diego Haz’s `base:` idea](https://x.com/diegohaz/status/1897776774206853269) and [a related `:where()` example](https://x.com/acd02/status/1897943176863756335).

#### Limitations

`base:` makes defaults overridable. It does not merge classes, so keep these rules in mind:

* **Conditional defaults lose too**: any ordinary `bg-*` utility beats `base:hover:bg-*`, `base:sm:bg-*`, and `base:dark:bg-*`, even while the condition matches. Write state styles without `base:`.
* **Class order still doesn’t matter**: two ordinary utilities that conflict, or two `base:` utilities that conflict, resolve by stylesheet order.
* **Put `base:` before pseudo-element variants**: `base:before:bg-*` works, but `before:base:bg-*` compiles to an invalid selector and never applies.
* **`!important` flips the layer order**: `base:bg-blue-600!` beats `bg-violet-600!`.

For runtime conflict resolution, [`cn`](#cn) and [`tailwind-merge`](#tailwind-merge) remove earlier conflicting utilities from the class string.

### tailwind-merge

Install [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) and combine it with the `cx` already exported by `cva`. This preserves strings, arrays, and conditional objects while resolving Tailwind CSS conflicts:

```sh
pnpm add tailwind-merge
```

cva.config.ts

```ts
import { defineConfig } from "cva/config";
import { cx as joinClasses } from "cva";
import { twMerge } from "tailwind-merge";


export const { cva, cx: cn } = defineConfig({
  cx: (...inputs) => twMerge(joinClasses(...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

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](https://cva.style/beta/api-reference#advanced-concatenators) for `clsx/lite`.
