Concepts
Variants
Define named style choices and infer their TypeScript types.
On this page
Give a component choices such as small and large with vn(). Each choice maps to a class string, and TypeScript checks that callers use one of the names you defined.
Create the map near the component that consumes it. This tone map belongs in a component module; it does not create CSS rules, it only returns the selected class string.
import type { ReactNode } from 'react'
import { cn, vn } from '../lib/style'
const $tone = vn({
info: 'border-blue-300 bg-blue-50 text-blue-950',
warning: 'border-amber-300 bg-amber-50 text-amber-950',
})
type NoticeProps = {
tone: typeof $tone.infer
children: ReactNode
}
export function Notice({ tone, children }: NoticeProps) {
return (
<aside className={cn('rounded border p-4', $tone(tone))}>{children}</aside>
)
}$tone.infer is the union "info" | "warning". A caller can pass <Notice tone="warning">Check the date.</Notice>, and TypeScript reports an error for a value such as tone="success" because that key is not in the map.
vn does not select a default on its own. Make a prop optional and set its JavaScript default in the component when omission should select a style; leave the prop required when callers must choose explicitly.
const $size = vn({ compact: 'text-sm', regular: 'text-base' })
type LabelProps = { size?: typeof $size.infer }
function Label({ size = 'regular' }: LabelProps) {
return <span className={$size(size)}>Ready</span>
}The parameter default makes <Label /> render with text-base. A required prop instead forces the caller to choose a key before the component can render. Keep each map flat and focused; if multiple choices need to be combined, use separate maps and join their results with cn in Composition.
vn() passes the selected map value through the cn function created by the same style() instance. This means custom serialization and composition apply to variant output too. For exact type and runtime signatures, see the API reference.