Variant files
Author class-variance-authority recipes so stories, types, and installs stay in sync.
Any file named *.variants.ts under registry/items/** must follow the recipe in Button.variants.ts. That file is the source of truth. Do not inline cva("classes", { variants }) or hardcode variant-name arrays.
Layout
Keep the recipe next to the component, then re-export it from the item index.ts:
registry/items/components/Example/
Example.vue
Example.variants.ts
Example.types.ts
index.ts
Recipe
Split shared classes, the variant map, and the cva() call. Always include compoundVariants (empty is fine) so the spread stays typed:
import { cn } from "cn";
import { cva } from "class-variance-authority";
const exampleBase = cn(
"inline-flex items-center rounded-md font-medium",
"disabled:opacity-50 disabled:pointer-events-none",
);
const exampleConfig = {
variants: {
variant: {
primary: "bg-primary text-primary-foreground",
outline: "border-border bg-background",
},
size: {
sm: "h-8 px-3 text-xs",
md: "h-10 px-4 text-sm",
},
},
compoundVariants: [],
defaultVariants: {
variant: "primary",
size: "md",
},
} as const;
export const exampleCva = cva(exampleBase, {
...exampleConfig,
compoundVariants: [...exampleConfig.compoundVariants],
});
export const EXAMPLE_VARIANTS = Object.keys(
exampleConfig.variants.variant,
) as (keyof typeof exampleConfig.variants.variant)[];
export const EXAMPLE_SIZES = Object.keys(
exampleConfig.variants.size,
) as (keyof typeof exampleConfig.variants.size)[];
export const exampleVariants = exampleCva;
Export one Object.keys list per string axis that stories or docs need (variant, size, tone, orientation, and so on). Skip boolean axes such as icon or fullWidth.
If the file defines more than one recipe (for example Bubble and BubbleReactions), repeat the same base → config → cva → keys → alias sequence for each.
Types and barrel
Put VariantProps aliases in <Component>.types.ts when that file exists. Derive them from the CVA export, not from a duplicated string union:
import type { VariantProps } from "class-variance-authority";
import type { exampleCva } from "./Example.variants";
type ExampleVariantProps = VariantProps<typeof exampleCva>;
export type ExampleVariant = ExampleVariantProps["variant"];
export type ExampleSize = ExampleVariantProps["size"];
Re-export the recipe from index.ts:
export { exampleVariants, exampleCva, EXAMPLE_VARIANTS, EXAMPLE_SIZES } from "./Example.variants";
Stories should import those key lists for Storybook argTypes.options instead of repeating names by hand.
Checklist
- Import
cnthencva. - Name the helpers
{name}Base,{name}Config,{name}Cva, and{name}Variants. - Keep
compoundVariantson the config object, even when the array is empty. - Spread
compoundVariantswhen callingcva()soas conststays compatible. - Derive published key lists from
Object.keys(config.variants.*). - List the
.variants.tsfile in_registry.mdxfileswhen the item publishes it.