The variant system
ai2's core idea: a component's look is a point in a small coordinate system, not an entry in a flat list.
The problem with flat variants
Most libraries model appearance as one variant prop. That works until visual weight and meaning need to move independently - a destructive action that should be quiet, a success state that should shout. Then you end up writing one-off CSS:
// The flat-list approach: every combination is a new name…
<Button variant="destructive">Delete</Button>
// …and the moment you need "subtle destructive", you write CSS:
<Button className="bg-red-50 text-red-600 hover:bg-red-100">
Delete
</Button>Three orthogonal axes
variant- visual weight: solid, soft, outline, ghost, link.tone- meaning: neutral, brand, success, warning, danger, info. Mapped to theme tokens, never hardcoded.size- scale: xs through xl where it matters.
// The axis approach: combinations are props.
<Button variant="soft" tone="danger">Delete</Button>
<Button variant="outline" tone="danger">Delete</Button>
<Button variant="ghost" tone="danger" size="sm">Delete</Button>Principles
- Axes only where meaningful. Separator has no tones; Badge does. No component carries an axis for symmetry's sake.
- Safe defaults. Every axis is optional -
<Button>is neutral, solid, medium. - Tokens underneath. Tones resolve to CSS variables, so themes recolor the whole matrix at once.
- Inspectable. Every element carries
data-slot, tone-aware ones alsodata-tone- for styling, testing and agents reading the DOM.
The Axis Contract
The axes above are not a loose convention - they are a fixed contract that every component obeys. The vocabulary is closed: variant (visual language), tone (semantic color), size (scale), density (list and table spacing), shape (geometry) and orientation (direction). No component invents a new axis name, so once you have learned one matrix you have learned them all.
Three standard tone sets
Tones come in exactly three fixed sets. A component picks the set that matches its job - never a custom subset:
| Set | Tones | Components | Role |
|---|---|---|---|
| FULL | neutral / brand / success / warning / danger / info | Button, Badge, Alert | Feedback surfaces |
| CONTROL | neutral / brand / success / danger | Checkbox, Switch, Radio Group | Selection controls |
| STATUS | neutral / success / danger | Input, Textarea, Select, Combobox | Validation states |
On form fields the STATUS set is the validation look: the tone colors the border and the focus ring, nothing else.
One control height scale
Every size axis reads from a single height table, aligned to Button. A component may expose a subset (Input stops at sm - lg), but never a different height - so a md Button, Input and Select trigger always sit flush in one row.
| Size | Height |
|---|---|
| xs | h-7 (28px) |
| sm | h-8 (32px) |
| md | h-9 (36px) |
| lg | h-10 (40px) |
| xl | h-12 (48px) |
Recent additions under the contract
Extending an axis is always additive - defaults never change, so existing consumers render identically. The latest wave brought form controls up to the full contract:
- Input - new
tone(neutral / success / danger). - Textarea - new
size(sm / md / lg) andtone(neutral / success / danger). - Select - the trigger now carries the full Input contract:
variant(outline / soft / ghost),size(sm / md / lg) andtone(neutral / success / danger). - Combobox - new
variant,sizeandtone, matching Select. - Toggle - new
tone(neutral / brand) for the pressed state; Toggle Group passes it down via context. - Field - new
orientation(vertical / horizontal) for switch-style rows. - Label - new
size(sm / md).
Matrix highlights
A sample - every component page documents its own axes in full:
| Component | Axes | Combinations |
|---|---|---|
| Button | variant (5) × tone (6) × size (10) | 300 |
| Badge | variant (3) × tone (6) × size (3) | 54 |
| Alert | variant (2) × tone (6) | 12 |
| Card | variant (4) × inset (3) | 12 |
| Progress | tone (6) × size (3) | 18 |
| Switch | tone (4) × size (3) | 12 |
| Checkbox | tone (4) × size (3) | 12 |
| Input | variant (3) × size (3) × tone (3) | 27 |
| Select | variant (3) × size (3) × tone (3) | 27 |
| Tabs | variant (3) | 3 |
| Sheet | side (4) | 4 |