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.

src/components/Notice.tsx
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.

infer is a type helper

Use typeof $tone.infer in a type position. Its runtime value is undefined; call $tone('warning') for classes. Give an optional prop a default before passing it to the variant function.

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.

tsx
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.