# Variants

> Define named style choices and infer their TypeScript types.

## Useful when

- Your component has named sizes, tones, or states.
- You want component props to stay in sync with their class map.
- You need to choose which variant props are required or have defaults.

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.

## Define and type a choice

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.

```tsx filename="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.

> **Note: 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.

## Choose required and defaulted props

`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](https://hulla.dev/docs/style/composition.md).

`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](https://hulla.dev/docs/style/reference.md).
