cva: the Class Variance Authority rebuilt with entry points and measured bundles
Class Variance Authority
At a glance
- What is it?
- The package behind a lot of shadcn component styling is being rewritten from scratch, split into subpath exports, and now shipping a stylesheet that works without any runtime class merging.
- Who is it for?
- cva is mid-rewrite, and that fact should shape how you approach it. Twelve betas have already shipped, each removing deprecated APIs from the one before, and beta.12 removed the `cva/utils` alias outright while pointing users at a migration skill rather than a codemod.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 16 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A README that is only badges, and a changelog that is the real documentation
It is worth stating plainly what this repository's README contains: a wallpaper image, the acronym expanded as Class Variance Authority, a link to cva.style, and a row of badges for npm version, included types, minizipped size, license, weekly downloads and a Bluesky profile. That is the entire file. There is no usage example, no install command, no API description.
Everything a reader needs is in the documentation site, and everything a maintainer needs is in the release notes. This is an unusual arrangement but a defensible one for a package that has published a dozen breaking betas in a row, since the README would have been rewritten repeatedly anyway.
The badges are not decorative, though. Minizipped size is tracked on bundlephobia, and the release notes quote measured byte counts, which means the author treats bundle weight as a shipped feature rather than an afterthought. Types are tracked as a separate badge, which for a library whose entire job is type inference is the right thing to surface.
The repository tree confirms the shift toward a monorepo with tooling around it. There are `packages/`, `examples/beta/` and `examples/latest/` side by side, a `docs/` site, `scripts/`, `test/`, plus `AGENTS.md`, `CLAUDE.md`, a `REVIEW.md`, a `skills/` directory with a `skills-lock.json`, and dot-directories for `.claude/`, `.zed/`, `.conductor/` and `.config/`.
Twelve betas and three import migrations in as many weeks
The pattern across beta.10, beta.11 and beta.12 is a deliberate deprecation schedule. Each release announces a warning block about breaking changes, names the APIs being removed, and points at the `cva-migrate` Agent Skill for upgrading from the earlier betas.
The migrations are specific and mechanical. In beta.10, `defineConfig` moved to the `cva/config` subpath and `getSchema` moved to `cva/tools`, with the old imports still working but deprecated. The same release added a one-command setup path: run `pnpm dlx shadcn@latest add joe-bell/cva/cn` to install `cva` alongside `cn`.
In beta.11, the deprecations were enforced. `compose(a, b)` was replaced by `cva({ composes: [a, b] })`, `defineConfig` and `getSchema` had to come from the new subpaths, and the `hooks` option was replaced by a custom `cx` wrapper. The notes are candid that `Compose` as a type has no replacement at all.
Then beta.12 removed the deprecated `cva/utils` entry point entirely, leaving `cva/tools` as the only home for `getSchema` and `GetSchema`, with a linked pull request describing the full details. A package that removes three entry points in three weeks is making a statement about API shape, and readers who were on the stable `class-variance-authority` line should read the migration guide before assuming continuity.
Bundle numbers quoted in the release notes
beta.11 reports concrete size reductions after the API cleanup. The minified, Brotli-compressed `cva` entry dropped from 2,104 B to 1,624 B, which the notes describe as a 23% reduction. The `cva/config` entry dropped from 1,608 B to 1,412 B, a 12% reduction.
Quoting both the absolute and relative figures is a small thing, and it is the kind of small thing that suggests the numbers came from a real measurement rather than a round number. It also suggests that removing API surface actually removed code, which in a package this small is not automatic.
The performance story in beta.10 is louder. The notes claim up to 1200% faster component calls in compound-variant benchmarks compared with `class-variance-authority`, and a 10% faster type check on a 200-component test project with 26% fewer type instantiations. Those are the maintainer's published benchmark numbers for a specific benchmark, not a general guarantee, and the underlying rationale is more interesting than the multiplier: configurations are now read when you create the component, so the recommended pattern is to create a new component rather than mutate an existing config. That change moves work out of the per-render path entirely, which is where the claimed gain comes from.
The benchmark tooling is visible in the repository's `package.json`, with scripts for baselines, check, compare and preview, plus a dedicated `bench` task. There is also a separate `bundlesize` script run across packages in parallel.
The cva/tailwindcss stylesheet that skips runtime merging
The most substantive new feature in beta.12 is an optional stylesheet of custom variants for Tailwind, usable with or without `cva` itself. It is imported as a second stylesheet alongside Tailwind's own, with the specifier `cva/tailwindcss`, so a project opts into it by adding one `@import` line after importing Tailwind CSS.
The mechanism is worth understanding because it is a different model from what the JavaScript API does. You prefix component defaults with `base:`, so a default might read `base:bg-blue-600`, and ordinary utilities like `bg-violet-600` override them through the CSS cascade. The release notes are explicit about the consequence: no runtime class merging required.
That is a real architectural choice rather than a convenience feature. The JavaScript approach has to read a config object, resolve the variant combination, and produce a class string at runtime. This approach hands the override to the stylesheet, so the browser does the cascade. For components with many variants, that moves real work out of JavaScript and into CSS, which is where CSS is good at it.
The notes link to a usage and limitations page rather than explaining the boundaries inline, which is the right call and also a signal: a feature described in one release as new will have edges the documentation does not yet settle.
What the package.json reveals about the project's own tooling
The root `package.json` is more informative than the README about how this repository is run. It is a private workspace root using pnpm, enforced by a `preinstall` hook that runs `npx only-allow pnpm`, and there is a `pnpm-workspace.yaml` and `pnpm-lock.yaml` alongside it.
Testing is vitest with coverage, run through a config file under `.config/`, and there is a separate `dev` script running vitest in watch mode. Type checking of the scripts themselves is its own task, `check:scripts`, which runs tsc against a dedicated tsconfig with no emit.
Formatting and consistency are handled by prettier with a plugin for package.json, which is a reasonable choice in a monorepo where package metadata drift is a recurring problem. `syncpack` has both `fix` and `lint` scripts, enforcing consistent dependency versions across workspace packages. There is a `lint-staged` script pointing at its own config, and `prepare:hooks` sets git's hooks path to a directory in `.github/hooks`.
The two entries that stand out are `lint:skills`, which runs skill-check in strict mode with the security scan disabled, and the presence of `skills-lock.json` and `skill-check.config.json`. The project ships skills as a versioned, lint-checked artifact, which matches the migration guidance in the release notes that points users at the `cva-migrate` Agent Skill rather than a script.
Licensing, activity, and where the docs actually live
The package is Apache-2.0 licensed, attributed to Joe Bell, and the README links both the license file and the author's site. The repository is not archived, its last push was on 2026-09-21, and it sits at roughly 6,892 stars against 139 forks with zero open issues. That fork ratio is unusual, around fifty to one, and it is more consistent with a package consumed as a dependency than with one people contribute back to.
The zero open issues deserves a caveat rather than celebration. It may reflect a healthy tracker, or it may reflect where questions go. The release notes mention that the documentation now shows measured bundle sizes and weekly npm downloads, which is the kind of transparency that tends to come with a well-run project, and the tree contains a `CODE_OF_CONDUCT.md` and a `CONTRIBUTING.md`.
Documentation lives at cva.style, and the release notes show it has a beta section, with pages for getting started, installation, tools, an API reference for `cva/config`, and a what-is-new page. Documentation for a package in the middle of a twelve-beta rewrite is the thing most likely to be behind the code, and given that three import paths changed in three weeks, reading it is not optional.
Editorial conclusion
cva is mid-rewrite, and that fact should shape how you approach it. Twelve betas have already shipped, each removing deprecated APIs from the one before, and beta.12 removed the `cva/utils` alias outright while pointing users at a migration skill rather than a codemod. What you get in exchange is a package with genuinely split entry points, measured bundle reductions quoted in the notes, and an optional Tailwind stylesheet that can handle component variants through the CSS cascade instead of runtime string building. The `cva/tailwindcss` option is the interesting direction, since it removes the JavaScript from the critical path for styling, but the release notes link to a limitations page, which suggests it is not yet a clean replacement. Read the current docs at cva.style rather than working from memory of `class-variance-authority`, since the import paths changed three times in three weeks.
Frequently asked questions
What does class-variance-authority do?
It is the package shadcn-based component libraries are built on for styling variants. You define a component's base classes plus named variants in a config object, then call it with props and get a class string back. The JavaScript model resolves the variant combination at runtime, which is why a class merging function such as `cn` is usually paired with it.
What changed in cva 1.0 beta?
The 1.0 line is a rewrite that moves APIs onto subpath exports rather than a single entry point. `defineConfig` now comes from `cva/config` and `getSchema` from `cva/tools`, `compose(a, b)` became `cva({ composes: [a, b] })`, and `hooks` was replaced by a custom `cx` wrapper. The old `cva/utils` alias was removed in beta.12.
How do I upgrade from an earlier cva beta?
The release notes point to the `cva-migrate` Agent Skill in the documentation, which covers upgrades across the beta range rather than one specific step. There is no single codemod for all of it, since the import path for the same symbol changed in beta.10, was enforced in beta.11, and had its alias removed in beta.12.
What is cva/tailwindcss and do I still need class merging?
It is an optional stylesheet of custom variants for Tailwind, importable alongside `tailwindcss` and usable with or without `cva` at runtime. You prefix component defaults with `base:`, such as `base:bg-blue-600`, and ordinary utilities override them through the CSS cascade, so no runtime class merging is required. The release notes link to a separate page covering its limitations.
Is cva the same as class-variance-authority?
The name is a continuation but the implementation is not. cva publishes its own 1.0 beta line and quotes compound-variant benchmarks against `class-variance-authority` as a comparison point. It is Apache-2.0 licensed and ships as `cva`, while the older package remains a separate project with its own versioning.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/joe-bell-cva)