mrkeyoor.com_
Sun 20 Sept 11:48 UTC
npmWeb Frontendupdated 20 Sept 2026

class-variance-authority review

class-variance-authority 0.7.1 turns a finite set of component choices into a function that returns one class string. You define names such as `tone`, `scale`, and `disabled`, map their allowed values to classes, and use `VariantProps` to derive the matching TypeScript props. It has no React or Tailwind peer, so the same selector can sit behind any view layer. Our browser build came to 1.1 KB minified and 0.6 KB gzipped. The current stable release adds license comments and changes the `clsx` dependency from a fixed version to `^2.1.1`; it does not add a new styling feature. The newer `cva` package is a separate 1.0 beta, not the npm `latest` version covered here.

Verdict

Our install of class-variance-authority 0.7.1 finished in 0.3 seconds, used 1 MB across 2 packages, added 0.6 KB gzipped, and produced 0 audit findings. It is worth installing when typed variant tables replace repeated component branches, provided your project already has a plan for Tailwind conflicts and multi-slot components.

We installed it

Lab card: what happened when we installed class-variance-authorityScreenshot of class-variance-authority documentation
Install✓ · 0.3s2 packages on disk · 1 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser0.6 KBgzipped (1.1 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does class-variance-authority install cleanly?

Yes. In a fresh container with an empty cache, npm install class-variance-authority finished in 0.3s, leaving 2 packages and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does class-variance-authority add to a browser bundle?

0.6 KB gzipped (1.1 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does class-variance-authority work with both ESM and CommonJS?

Yes. Both import 'class-variance-authority' and require('class-variance-authority') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does class-variance-authority include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

class-variance-authority or clsx: which should you use?

clsx: Use it when you only need conditional class joining and do not need variant prop types or compound rules. Our install of class-variance-authority 0.7.1 finished in 0.3 seconds, used 1 MB across 2 packages, added 0.6 KB gzipped, and produced 0 audit findings.

When should you not use class-variance-authority?

The component has a single boolean condition or two short branches. clsx expresses that directly without introducing a variant schema.

API stability5/5Version 0.7.1 exposes two runtime functions, `cva` and `cx`, plus the `VariantProps` type. Its options remain limited to `variants`, `defaultVariants`, and `compoundVariants`. The 0.7.1 release changed license comments and the accepted `clsx` range without changing calls in application code. The small stable surface is easy to pin, though examples for the separate 1.0 beta cannot be copied into this package.
Docs4/5The 0.7.1 documentation shows default values, boolean variants, exact and multi-value compound matches, extra `class` input, TypeScript prop extraction, and the utility-type recipe for required fields. It also says that stable composition is manual and documents two Tailwind conflict strategies. Version navigation is the weak spot: a prominent banner points to the separate beta, whose package name and composition API do not apply to 0.7.1.
Maintenance3/5npm still serves 0.7.1, published on 2024-11-26, as the stable `latest` release. GitHub was pushed on 2026-09-18 and had 1 open pull request when checked, so the repository is active. Most visible feature work now targets the separate `cva` 1.0 beta, including composition and configurable class merging. Stable users get a quiet API, but they should not assume beta fixes or features have reached `class-variance-authority`.
Ecosystem5/5npm recorded 48,324,665 downloads for the week ending 2026-09-19, and GitHub showed 6,890 stars. The package has no framework peer and returns an ordinary string, which makes it usable from React, Vue, Svelte, server templates, and static generation. Its narrow boundary also explains the companion packages developers often add: `clsx` is already included, while slot handling and Tailwind conflict resolution remain separate choices.

Use it if

  • A component has several named choices whose allowed values should become TypeScript unions instead of loose strings.
  • Compound styling depends on an exact combination, such as a warning tone only when the control is enabled.
  • One variant definition must work outside React, including server templates, Vue, Svelte, or static rendering.
  • Your design system wants the class table next to the component while leaving CSS generation to Tailwind or ordinary stylesheets.
Skip it if

Setup reality

We installed class-variance-authority 0.7.1 in our fresh Node 22 Bookworm sandbox, and npm finished in 0.3 seconds. The result occupied 1 MB across 2 packages. npm audit reported 0 known vulnerabilities: 0 critical, 0 high, 0 moderate, and 0 low. The package itself has 1 direct dependency, no peer dependencies, and 52 KB unpacked under Apache-2.0. Bundled TypeScript declarations were present. Both CommonJS require() and ESM import worked through the exports map. Our esbuild check produced 1.1 KB minified and 0.6 KB gzipped.

Version 0.7.1 needs no account, environment variable, provider, or CVA config file. Variant definitions are plain JavaScript or TypeScript, and Tailwind is optional. Tailwind editor completion inside cva() and cx() does need a classFunctions entry in supported editor settings. That setting changes autocomplete only; it does not change the classes returned at runtime.

CVA passes its inputs to the single clsx dependency and leaves conflicting utilities in the string. If the selector emits px-2 and a caller adds px-6, class order in the HTML is not an override guarantee because Tailwind's generated CSS order decides the result. The official docs give two remedies: a Tailwind 4 base: cascade recipe or a tailwind-merge wrapper. Either choice becomes part of your styling policy.

Defaults apply when a property is omitted or undefined; passing null removes that default for one call. Compound entries require every named condition to match, and an array accepts any listed value. Stable 0.7.1 returns synchronously, stores no cache, and has no built-in slot or composition object. Compose separate selectors with cx, and enforce required variants in your own TypeScript type.

Patterns

Turn button choices into a class selector define-button-variants

import { cva } from 'class-variance-authority';

export const buttonStyles = cva('inline-flex items-center rounded font-medium', {
  variants: {
    tone: {
      primary: 'bg-indigo-600 text-white',
      danger: 'bg-red-600 text-white',
    },
    scale: {
      sm: 'h-8 px-3 text-sm',
      lg: 'h-11 px-5 text-base',
    },
  },
});

buttonStyles({ tone: 'primary', scale: 'sm' });

`cva()` in 0.7.1 returns a function, and that function produces one class string from the configured variant values.

Fill in choices the caller omits add-default-variants

const tagStyles = cva('inline-flex rounded-full', {
  variants: {
    tone: { neutral: 'bg-zinc-100', info: 'bg-sky-100' },
    scale: { sm: 'px-2 py-0.5 text-xs', md: 'px-3 py-1 text-sm' },
  },
  defaultVariants: {
    tone: 'neutral',
    scale: 'md',
  },
});

tagStyles();

`defaultVariants` is consulted when a value is absent or `undefined`; the returned string includes both defaults in this call.

Map a boolean state to classes model-boolean-variant

const inputStyles = cva('rounded border px-3 py-2', {
  variants: {
    invalid: {
      true: 'border-red-600 text-red-900',
      false: 'border-zinc-300',
    },
  },
  defaultVariants: { invalid: false },
});

inputStyles({ invalid: true });

The configuration uses `true` and `false` object keys, while `VariantProps` exposes `invalid` as a boolean value.

Add classes for one exact combination add-compound-variant

const alertStyles = cva('rounded border p-4', {
  variants: {
    tone: { info: 'border-sky-300', danger: 'border-red-300' },
    dismissible: { true: 'pr-12', false: null },
  },
  compoundVariants: [
    {
      tone: 'danger',
      dismissible: true,
      class: 'relative font-semibold',
    },
  ],
});

A compound rule runs only when every condition in that entry matches; its `class` value is appended after normal variant classes.

Share one compound rule across values target-multiple-values

const chipStyles = cva('rounded-full px-2', {
  variants: {
    tone: { neutral: 'bg-zinc-100', warning: 'bg-amber-100', danger: 'bg-red-100' },
    outlined: { true: 'border', false: null },
  },
  compoundVariants: [
    {
      tone: ['warning', 'danger'],
      outlined: true,
      className: 'border-current',
    },
  ],
});

An array inside a compound condition means any listed value can match. Stable 0.7.1 has no inverse or not-equal condition.

Derive props from the variant table extract-variant-props

import { cva, type VariantProps } from 'class-variance-authority';

const textStyles = cva('', {
  variants: {
    weight: { regular: 'font-normal', bold: 'font-bold' },
    muted: { true: 'text-zinc-500', false: 'text-zinc-950' },
  },
});

type TextStyleProps = VariantProps<typeof textStyles>;

`VariantProps` reads the selector argument type and removes its extra `class` and `className` fields. Configured variants stay optional.

Require one choice with TypeScript require-variant-prop

type InferredProps = VariantProps<typeof buttonStyles>;

type ButtonStyleProps =
  Omit<InferredProps, 'tone'> &
  Required<Pick<InferredProps, 'tone'>>;

function getButtonClass(props: ButtonStyleProps) {
  return buttonStyles(props);
}

CVA 0.7.1 has no required-variant flag. The official guide uses TypeScript utility types to make selected fields mandatory.

Connect variant props to a React button use-in-react-component

import type { ButtonHTMLAttributes } from 'react';
import type { VariantProps } from 'class-variance-authority';

type Props = ButtonHTMLAttributes<HTMLButtonElement> &
  VariantProps<typeof buttonStyles>;

export function Button({ tone, scale, className, ...rest }: Props) {
  return (
    <button
      {...rest}
      className={buttonStyles({ tone, scale, className })}
    />
  );
}

CVA has no React peer dependency. The component owns DOM props and passes only variant values plus `className` to the selector.

Accept a one-off class from the caller append-caller-classes

const labelStyles = cva('text-sm font-medium', {
  variants: {
    muted: { true: 'text-zinc-500', false: 'text-zinc-900' },
  },
});

labelStyles({
  muted: true,
  className: 'mb-2 block',
});

Every selector accepts `class` or `className` and appends that input. It does not inspect the added utilities for CSS conflicts.

Combine two stable selectors with cx compose-variant-functions

import { cva, cx, type VariantProps } from 'class-variance-authority';

const spacing = cva('', {
  variants: { compact: { true: 'p-2', false: 'p-6' } },
});
const surface = cva('rounded', {
  variants: { elevated: { true: 'shadow-md', false: 'border' } },
});

type CardStyleProps = VariantProps<typeof spacing> &
  VariantProps<typeof surface>;

const cardStyles = ({ compact, elevated }: CardStyleProps = {}) =>
  cx(spacing({ compact }), surface({ elevated }));

The stable package has no composition method. Its docs combine selector results with `cx`, the package's `clsx` alias.

Resolve Tailwind conflicts after selection merge-tailwind-overrides

import { twMerge } from 'tailwind-merge';

function getButtonClass(
  props: Parameters<typeof buttonStyles>[0],
) {
  return twMerge(buttonStyles(props));
}

getButtonClass({ scale: 'sm', className: 'px-8' });

CVA keeps both padding utilities. `tailwind-merge` is the separate package that makes the later `px-8` replace the earlier horizontal padding.

Remove a default for one call suppress-default-variant

const panelStyles = cva('rounded', {
  variants: {
    tone: { plain: 'bg-white', muted: 'bg-zinc-100' },
  },
  defaultVariants: { tone: 'plain' },
});

panelStyles({ tone: null }); // rounded

Passing `null` disables the configured default for that invocation. Passing `undefined` would keep the `plain` classes.

Alternatives

PackageRegistryPick it when
clsxnpmUse it when you only need conditional class joining and do not need variant prop types or compound rules.
tailwind-variantsnpmUse it when a Tailwind component needs typed variants plus named slots for parts such as the icon and label.
tailwind-mergenpmUse it when the job is resolving conflicting Tailwind utilities rather than defining a component variant table.

More web frontend guides

postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.