CtrlK
BlogDocsLog inGet started
Tessl Logo

cva-best-practices

Build and review typed component variants with cva. Use when defining cva components, exposing variant props, composing styles, handling class conflicts, or generating variant galleries. For version upgrades, use migration guidance instead.

68

Quality

83%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

SKILL.md
Quality
Evals
Security

cva best practices

Use the project's styling approach and component conventions. Apply the guidance below to the requested component rather than refactoring unrelated code. The beta docs are the source of truth for these recommendations.

Check the installed package

Read the consuming package's manifest, lockfile, and existing imports before choosing an API. cva@1.0.0-beta.x and class-variance-authority@0.x are different packages. Beta releases can change without semver guarantees; verify the installed exports and types when an API below is unavailable. Do not upgrade dependencies as part of ordinary component work.

This guidance targets the cva 1.0 betas (verified against cva@1.0.0-beta.12): the single-object cva call, composes, defineConfig from cva/config, and getSchema from cva/tools. Older betas may have different entry points or APIs. For stable class-variance-authority, follow the stable docs: import from class-variance-authority and use cva(base, options). Do not apply beta-only configuration, composition, schema, or Tailwind CSS exports to stable projects. When an upgrade is requested, consult the release-specific migration guidance instead.

Define variants once

  • Define a class function outside the component render body. Put invariant classes in base, independent choices in variants, and combinations in compoundVariants.
  • Prefer framework prop defaults in React, Svelte, or Vue wrappers so the same value reaches styling and markup. defaultVariants only supplies defaults inside the class function; it cannot set an element's attributes. Use it when the class function should supply defaults to its callers. Omitted props and undefined use those defaults. Avoid duplicating defaults in both places, and remember getSchema cannot read framework prop defaults.
  • To select no classes for a variant, declare a named option such as unset: null, then pass "unset". Do not assume passing null disables a beta variant.
  • Treat the configuration and referenced objects as immutable after creating a class function. Create another function if the configuration changes.
  • Prefer server-side rendering or static generation for static components when the framework permits it. Do not add client-side JavaScript solely to generate a static class string.

See Variants, Default variants, and the cva API reference.

Keep props inferred

Use VariantProps<typeof button> rather than repeating variant unions. It contains public variant props, not class or className, and omits variant names prefixed with _. Internal variants still work in defaults, compound variants, and direct calls; they are not a runtime access-control mechanism.

For a React wrapper, combine variant types with native element props and forward className to the class function. If names overlap incompatibly, omit the overlapping native props before combining them. Forward semantic props such as disabled to the actual HTML element as well as the class function. Use TypeScript's Required<Pick<...>> and Omit when a public variant must be required.

See Extracting variant types and the React gallery.

Join classes without another dependency

The preset exports cx, backed by clsx, for strings, nested arrays, and conditional objects. Use it instead of adding clsx or classnames for ordinary conditional class joining. cx does not deduplicate classes or resolve CSS conflicts.

For Tailwind CSS, write complete utility class strings in variant definitions. Do not interpolate fragments such as bg-${tone}-500: Tailwind scans source text and cannot infer the resulting names. Map variant values to static class strings instead, as shown in Installation.

Pass extra classes through the class function's class or className prop. Appending a utility does not guarantee a CSS override. For Tailwind CSS, follow the project's existing conflict strategy:

  • cn: import { cn as merge } from "cn", then defineConfig({ cx: merge }).
  • cva/tailwindcss: import after Tailwind CSS and prefix overridable component defaults with base:, including defaults selected by variants or compound variants. Ordinary utilities override them through the cascade. Keep hover and disabled state styles ordinary; any ordinary utility also beats conditional base: defaults. Put base: before pseudo-element variants, and remember important declarations reverse layer priority.
  • tailwind-merge: combine its twMerge with the preset cx to preserve conditional objects. Bare twMerge has a narrower input grammar.

For the last option:

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)),
});

Import the configured functions throughout that project. Do not accidentally use the preset for components expected to merge conflicts. A custom concatenator owns the input grammar and must support empty calls, variadic inputs, and composed strings.

See Handling class conflicts and Extending Components.

Compose classes and preserve semantics

Use composes for reusable cva class functions. Pass one function or an inline array; use as const for a stored array to retain tuple inference. Overlapping variant values add each component's matching classes. Defaults merge with the last composed default winning, then local defaults take precedence across the composition.

A cva class function produces a string, so apply it to the appropriate HTML element. For a React render-prop API, the docs recommend Base UI's useRender; do not invent a cva styled API. For compound component styling, consider the CSS cascade, custom properties, and selectors such as :has() before introducing JavaScript state solely to share styles.

cva does not interpret responsive variant objects. Express responsive styles in your CSS, define a named variant with breakpoint utilities, or show/hide variants at breakpoints as appropriate for the existing UI.

See Composing Components, Compound Components, Polymorphism, and FAQs.

Generate galleries from the schema

Call getSchema on the cva class function, not its React or other framework wrapper. Read the schema outside rendering and iterate typed values to generate galleries, documentation, or controls without duplicate lists. Boolean and numeric values retain their types; internal and empty variants are omitted. defaultValue is present only when that variant has a declared default.

Keep an omitted-prop case when demonstrating defaults. Derive layout counts from the displayed schema arrays rather than hard-coding them. Put schema consumers in stories or documentation when the application does not need them at runtime.

See Tools. Verify changed class outputs against the installed package and type-check consuming components using the project's checks.

Repository
joe-bell/cva
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.