<!-- source: https://github.com/voxel51/design-system | synced: 665ef52 | 2026-05-29 -->

# VOODO (`@voxel51/voodo`) — LLM Reference

> Self-contained reference for LLM-assisted code generation with the [Voxel51 design system](https://github.com/voxel51/design-system).
> All components are imported from `@voxel51/voodo`. CSS theme: `@voxel51/voodo/theme.css`.

---

## Design Tokens

Use these enums for all prop values. Never pass raw strings — always use the enum member.

### Layout

```ts
enum Orientation { Row = "row", Column = "col" }
enum Align     { Baseline = "baseline", Center = "center", End = "end", Start = "start" }
enum Justify   { Around = "around", Between = "between", Center = "center", End = "end", Evenly = "evenly", Start = "start" }
enum Spacing   { None = "none", Xs = "xs", Sm = "sm", Md = "md", Lg = "lg", Xl = "xl" }
```

### Sizing & Shape

```ts
enum Size    { Xs = "xs", Sm = "sm", Md = "md", Lg = "lg", Xl = "xl" }
enum Radius  { Full = "full", Xl = "xl", Lg = "lg", Md = "md", Sm = "sm", Xs = "xs", None = "none" }
enum Shadow  { None = "none", Xs = "xs", Sm = "sm", Md = "md", Lg = "lg", Xl = "xl" }
```

### Color

```ts
enum Variant        { Primary = "primary", Secondary = "secondary", Success = "success", Danger = "danger", Icon = "icon", Borderless = "borderless" }
enum TextColor      { Fg = "text-fg", Primary = "text-primary", Secondary = "text-secondary", Tertiary = "text-tertiary", Muted = "text-muted", Placeholder = "text-placeholder", Success = "text-success", Destructive = "text-destructive", Warning = "text-warning", Info = "text-info" }
enum BrandColor     { Primary = "brand-primary", Accent = "brand-accent" }
enum IconColor      { Default = "icon-default", Subtle = "icon-subtle", Emphasis = "icon-emphasis", Muted = "icon-muted", Disabled = "icon-disabled", Decorative = "icon-decorative", Brand = "icon-brand", BrandAccent = "icon-brand-accent", Success = "icon-success", Destructive = "icon-destructive", Warning = "icon-warning", Info = "icon-info" }
enum BackgroundColor { Transparent = "bg-transparent", Background = "bg-background", Card1 = "bg-card-1", Card2 = "bg-card-2", CardElevated = "bg-card-elevated", Muted = "bg-muted", Popover = "bg-popover", Secondary = "bg-secondary" }
enum BorderColor    { Default = "border-default", Strong = "border-strong", Hover = "border-hover", Focus = "border-focus", Subtle = "border-subtle", Active = "border-active", Error = "border-error", Success = "border-success", Warning = "border-warning", Disabled = "border-disabled" }
enum SemanticColor  { Success = "semantic-success", Destructive = "semantic-destructive", Info = "semantic-info", Warning = "semantic-warning" }
enum StatusColor    { Approved = "status-approved", Review = "status-review", Progress = "status-progress", Default = "status-default", Failed = "status-failed" }
```

`ActionColor` (`PrimaryDefault`/`PrimaryHover`/`PrimaryFocus`/`PrimaryText`, and the `Secondary*`/`Success*`/`Danger*` equivalents, plus `IconDefault`) also exists, but it is **internal** — used by `Button`/`Toolbar` styling. Do not pass it to component props; use `Variant` instead.

### Typography

```ts
enum TextVariant  { Xxs = "xxs", Xs = "xs", Sm = "sm", Md = "md", Lg = "lg", Xl = "xl", Xxl = "xxl", Label = "label", Caption = "caption" }
enum HeadingLevel { H1 = "h1", H2 = "h2", H3 = "h3", H4 = "h4" }
```

### Positioning

```ts
enum Anchor { TopLeft = "top-left", Top = "top", TopRight = "top-right", Right = "right", BottomRight = "bottom-right", Bottom = "bottom", BottomLeft = "bottom-left", Left = "left" }
```

### Layering

Pass `ZIndex` to the `zIndex` prop of overlay components (`Dropdown`, `Select`, `Tooltip`, `Toolbar`, `TreeSelect`) instead of hardcoding `z-[...]` classes or inline `zIndex` styles.

```ts
enum ZIndex { Default = "default", Low = "low", Medium = "medium", High = "high", AboveModal = "above-modal" }
```

### Motion

Transition tokens for animating components. Used via the helper functions (`transitionDuration`, `transitionEasing`, `transitionPreset`, `transitionPresetValue`) which return Tailwind arbitrary-value classes / CSS `var(...)` strings. Prefer a `TransitionPreset` over composing duration + easing by hand.

```ts
enum TransitionDuration { Instant = "instant", Fast = "fast", Normal = "normal", Moderate = "moderate", Slow = "slow", Deliberate = "deliberate" }
enum TransitionEasing   { Linear = "linear", In = "in", Out = "out", InOut = "in-out", Spring = "spring", Sharp = "sharp" }
enum TransitionPreset   { Colors = "colors", Opacity = "opacity", Transform = "transform", Shadow = "shadow", Menu = "menu", Panel = "panel", Overlay = "overlay", All = "all" }
```

### Element State

Used with the `withElementState(cssClass, state)` styling helper to scope a class to an interaction state. Primarily for component authoring, not consumed directly in app code.

```ts
enum ElementState { Active = "data-active", AutoFocus = "data-autofocus", Checked = "data-checked", Disabled = "disabled", Dragging = "data-dragging", Focus = "data-focus", Hover = "hover", None = "none", Open = "data-open", Selected = "data-selected" }
```

### Icons

```ts
enum IconName {
  Add, AI, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, CaretDown, Check, Checkbox, Checklist,
  ChevronBottom, ChevronLeft, ChevronRight, ChevronTop, Circle, Close, Code, ContentCopy, DateRange,
  Draw, Delete, Detection, Drag, Edit, Embeddings, Enter, Error, ExitWorkspace, ExternalLink,
  FineTune, Fullscreen, GridView, ImageSearch, Info, Inspect, JSON, Lock, Logs, Menu, MoreHorizontal,
  MoreVertical, Move, Notes, Orchestrator, Pin, Polyline, Radio, Redo, Refresh, Remove, Search,
  Settings, Slider, Spinner, Tag, Text, Toggle, Undo, UnfoldMore, Unlock, Unsupported, VAL, Warning,
  Workspaces
}
```

---

## Components

### Layout

#### Stack
Flexbox layout primitive. Use this for all spacing and alignment — avoid raw Tailwind flex/gap/items/justify classes.

| Prop | Type | Default |
|------|------|---------|
| `orientation` | `Orientation` | `Orientation.Row` |
| `spacing` | `Spacing` | `Spacing.None` |
| `align` | `Align` | — |
| `justify` | `Justify` | — |

```tsx
<Stack orientation={Orientation.Column} spacing={Spacing.Md}>
  <Text>First</Text>
  <Text>Second</Text>
</Stack>

<Stack orientation={Orientation.Row} align={Align.Center} justify={Justify.Between}>
  <Text>Left</Text>
  <Button>Right</Button>
</Stack>
```

#### Divider
Visual separator line.

| Prop | Type | Default |
|------|------|---------|
| `orientation` | `Orientation` | — |
| `label` | `string` | — |
| `textProps` | `TextProps` | — |

---

### Typography

#### Text
Inline text element (`<span>`).

| Prop | Type | Default |
|------|------|---------|
| `variant` | `TextVariant` | `TextVariant.Md` |
| `color` | `TextColor \| IconColor \| BrandColor` | `TextColor.Primary` |

```tsx
<Text variant={TextVariant.Sm} color={TextColor.Secondary}>Subtitle</Text>
```

#### Heading
Block heading element (`<h1>`–`<h4>`).

| Prop | Type | Default |
|------|------|---------|
| `level` | `HeadingLevel` | `HeadingLevel.H1` |

---

### Containers

#### Card
Styled container with background, shadow, and padding.

| Prop | Type | Default |
|------|------|---------|
| `background` | `CardBackground` | `CardBackground.Primary` |
| `shadow` | `Shadow` | `Shadow.Md` |
| `compact` | `boolean` | — |
| `outlined` | `boolean` | — |

```ts
enum CardBackground { Primary = "primary", Secondary = "secondary", Elevated = "elevated" }
```

```tsx
<Card background={CardBackground.Secondary} compact>
  <Text>Compact secondary card</Text>
</Card>
```

#### RichCard
Card with built-in icon, title, description, badge, and action slot.

| Prop | Type | Default |
|------|------|---------|
| `icon` | `IconName` | — |
| `title` | `string` | — |
| `description` | `string \| string[]` | — |
| `badge` | `string` | — |
| `action` | `ReactNode` | — |
| `compact` | `boolean` | — |
| _...inherits all Card props_ |

```tsx
<RichCard
  icon={IconName.Settings}
  title="Configuration"
  description="Manage your workspace settings."
  badge="New"
  action={<Button size={Size.Sm}>Open</Button>}
/>
```

#### EmptyState
Centered placeholder for empty views. Extends `StackProps`.

| Prop | Type | Default |
|------|------|---------|
| `icon` | `IconName` | **required** |
| `title` | `string` | **required** |
| `description` | `string` | — |

#### Collapsible
Vertically collapsible container with an optional header trigger. Supports controlled (`open` + `onOpenChange`) and uncontrolled (`defaultOpen`) usage.

| Prop | Type | Default |
|------|------|---------|
| `header` | `(state: CollapsibleState) => ReactNode` | — |
| `animated` | `boolean` | `true` |
| `defaultOpen` | `boolean` | — |
| `open` | `boolean` | — |
| `onOpenChange` | `(open: boolean) => void` | — |

```ts
interface CollapsibleState { open: boolean; toggle: () => void }
```

```tsx
<Collapsible header={({ open, toggle }) => (
  <Clickable onClick={toggle}>
    <Stack align={Align.Center} spacing={Spacing.Sm}>
      <Icon name={open ? IconName.ChevronBottom : IconName.ChevronRight} size={Size.Sm} />
      <Text variant={TextVariant.Label}>Advanced options</Text>
    </Stack>
  </Clickable>
)}>
  <Text color={TextColor.Secondary}>Hidden content</Text>
</Collapsible>
```

#### Drawer
Resizable, collapsible panel anchored to an edge of its container. Drag the handle to resize; controlled/uncontrolled open state via `useDisclosure` options.

| Prop | Type | Default |
|------|------|---------|
| `maxSize` | `number` (px) | **required** |
| `side` | `DrawerSide` | `"bottom"` |
| `mode` | `"push" \| "float"` | `"push"` |
| `header` | `(state: DrawerHeaderState) => ReactNode` | — |
| `onSizeChange` | `(size: number) => void` | — |
| `defaultOpen` | `boolean` | `true` |
| `open` | `boolean` | — |
| `onOpenChange` | `(open: boolean) => void` | — |

```ts
type DrawerSide = "left" | "right" | "top" | "bottom";
interface DrawerHeaderState { open: boolean; toggle: () => void }
```

```tsx
<Drawer side="right" maxSize={480} header={({ open, toggle }) => (
  <Clickable onClick={toggle}><Text variant={TextVariant.Label}>Details</Text></Clickable>
)}>
  <DetailsPanel />
</Drawer>
```

---

### Actions

#### Button

| Prop | Type | Default |
|------|------|---------|
| `variant` | `Variant` | `Variant.Primary` |
| `size` | `Size` (Xs–Md only) | `Size.Md` |
| `leadingIcon` | `FC \| IconName` | — |
| `trailingIcon` | `FC \| IconName` | — |
| `borderless` | `boolean` | `false` |

```tsx
<Button variant={Variant.Danger} leadingIcon={IconName.Delete} onClick={onDelete}>
  Delete
</Button>
```

#### RichButton
Card-style button with icon, label, description, and active state.

| Prop | Type | Default |
|------|------|---------|
| `label` | `ReactNode` | — |
| `description` | `ReactNode` | — |
| `icon` | `FC \| IconName` | — |
| `active` | `boolean` | — |
| `onClick` | `() => void` | — |

#### RichButtonGroup
Manages a set of RichButtons with single or multi selection.

| Prop | Type | Default |
|------|------|---------|
| `buttons` | `Descriptor<RichButtonProps>[]` | **required** |
| `exclusive` | `boolean` | — |
| `onChange` | `(active: string[]) => void` | — |

#### Clickable
Minimal interactive wrapper (`<span>` with cursor pointer).

#### Toolbar
Floating, draggable tool palette. Compose with `ToolbarGroup` and `ToolbarAction`. Double-click the drag handle to collapse. Domain-agnostic — knows nothing about the tools it hosts.

| Prop | Type | Default |
|------|------|---------|
| `orientation` | `Orientation` | `Orientation.Column` |
| `lockX` | `boolean` | `false` |
| `lockY` | `boolean` | `false` |
| `xOffset` | `string \| number` | `20` |
| `yOffset` | `string \| number` | `20` |
| `zIndex` | `ZIndex` | `ZIndex.AboveModal` |
| `visible` | `boolean` | `true` |
| `onPositionChange` | `(pos: { x: number; y: number }) => void` | — |

**ToolbarGroup** — groups `ToolbarAction`s; reads orientation from context, auto-renders a divider after each group (last hidden). Props: `children`.

**ToolbarAction** — a single icon-button. Props: `active` (`boolean`, default `false`), `disabled` (`boolean`, default `false`), `onClick`, `children` (icon content), `aria-label`.

```tsx
<Toolbar orientation={Orientation.Column} xOffset={20} yOffset={100}>
  <ToolbarGroup aria-label="Draw">
    <ToolbarAction active={tool === "brush"} onClick={() => setTool("brush")} aria-label="Brush">
      <Icon name={IconName.Draw} />
    </ToolbarAction>
    <ToolbarAction active={tool === "move"} onClick={() => setTool("move")} aria-label="Move">
      <Icon name={IconName.Move} />
    </ToolbarAction>
  </ToolbarGroup>
</Toolbar>
```

---

### Menus

Composable menu primitives. Render them as children of `Dropdown` (click-triggered) or in the `menu` slot of `ContextMenu` (right-click triggered). Both are built on HeadlessUI's `Menu` — keyboard navigation and `role="menu"` ARIA semantics come for free.

#### Dropdown
Click-triggered menu.

| Prop | Type | Default |
|------|------|---------|
| `trigger` | `ReactNode` | **required** |
| `children` | `ReactNode` (Menu* items) | **required** |
| `anchor` | `DropdownAnchor` | `DropdownAnchor.BottomStart` |
| `portal` | `boolean` | `true` |
| `zIndex` | `ZIndex` | — |
| `disabled` | `boolean` | — |

```ts
enum DropdownAnchor { Bottom = "bottom", BottomStart = "bottom start", BottomEnd = "bottom end", Top = "top", TopStart = "top start", TopEnd = "top end" }
```

#### DropdownTrigger
The standard trigger button for a `Dropdown` — a secondary button with a trailing caret. Accepts `ButtonProps` except `variant` and `trailingIcon`.

| Prop | Type | Default |
|------|------|---------|
| `size` | `Size` (Xs–Md only) | `Size.Sm` |

#### ContextMenu
Right-click triggered menu; opens at the cursor and flips near viewport edges.

| Prop | Type | Default |
|------|------|---------|
| `children` | `ReactNode` (the right-clickable area) | **required** |
| `menu` | `ReactNode` (Menu* items) | **required** |
| `disabled` | `boolean` | — |

#### Menu items
| Component | Meaningful props |
|-----------|------------------|
| `MenuTextItem` | `children` (label), `destructive?: boolean`, `disabled?: boolean`, `onClick` |
| `MenuIconTextItem` | `icon: IconName \| ReactNode` **required**, `text: string` **required**, `subtext?: string`, `destructive?: boolean`, `disabled?: boolean`, `onClick` |
| `MenuCheckItem` | `checked?: boolean`, `disabled?: boolean`, `children` (label), `onClick` |
| `MenuSectionTitle` | `children: string` (uppercase muted label) |
| `MenuSeparator` | — (horizontal rule) |

```tsx
<Dropdown trigger={<DropdownTrigger>Actions</DropdownTrigger>}>
  <MenuSectionTitle>Actions</MenuSectionTitle>
  <MenuIconTextItem
    icon={IconName.ImageSearch}
    text="Sort by similarity"
    subtext="Find visually similar"
    onClick={sortBySimilarity}
  />
  <MenuSeparator />
  <MenuTextItem destructive onClick={onDelete}>Delete</MenuTextItem>
</Dropdown>
```

---

### Form Controls

#### Input

| Prop | Type | Default |
|------|------|---------|
| `size` | `Size` | `Size.Md` |
| `radius` | `Radius` | `Radius.Sm` |
| `type` | `InputType` | `InputType.Text` |
| `error` | `boolean` | `false` |
| `icon` | `FC \| IconName` | — |

```ts
enum InputType { Text, Email, Password, Number, Tel, Url, Search }
```

```tsx
<Input
  value={query}
  onChange={(e) => setQuery(e.target.value)}
  icon={IconName.Search}
  size={Size.Sm}
/>
```

#### TextArea

| Prop | Type | Default |
|------|------|---------|
| `size` | `Size` | `Size.Md` |
| `radius` | `Radius` | `Radius.Sm` |
| `error` | `boolean` | `false` |
| `resize` | `ResizeBehavior` | `ResizeBehavior.Vertical` |
| `rows` | `number` | `3` |

```ts
enum ResizeBehavior { None, Vertical, Horizontal, BiDirectional }
```

#### Select

| Prop | Type | Default |
|------|------|---------|
| `options` | `Descriptor<{ label: string; content?: ReactNode }>[]` | — |
| `value` | `string \| string[]` | — |
| `onChange` | `(value: string \| string[] \| null) => void` | — |
| `exclusive` | `boolean` | — |
| `anchor` | `SelectAnchor` | `SelectAnchor.BottomStart` |
| `disabled` | `boolean` | — |
| `portal` | `boolean` | — |
| `zIndex` | `ZIndex` | — |

```ts
enum SelectAnchor { Bottom, BottomStart, BottomEnd, Top, TopStart, TopEnd }
```

#### Checkbox

| Prop | Type | Default |
|------|------|---------|
| `checked` | `boolean` | — |
| `onChange` | `(checked: boolean) => void` | — |
| `label` | `string` | — |
| `disabled` | `boolean` | — |
| `size` | `Size` | `Size.Md` |
| `showUnsetHint` | `boolean` | — |

#### Toggle
Switch-style boolean control.

| Prop | Type | Default |
|------|------|---------|
| `checked` | `boolean` | — |
| `onChange` | `(checked: boolean) => void` | — |
| `label` | `string` | — |
| `disabled` | `boolean` | `false` |
| `size` | `Size` (Sm–Md only) | `Size.Md` |

#### Radio / RadioGroup

| Prop (RadioGroup) | Type | Default |
|------|------|---------|
| `options` | `RadioOption[]` | **required** |
| `value` | `string` | — |
| `onChange` | `(value: string) => void` | **required** |
| `size` | `Size` (Sm–Xl only) | `Size.Sm` |
| `disabled` | `boolean` | `false` |

```ts
interface RadioOption { value: string; label: string; disabled?: boolean }
```

```tsx
<RadioGroup
  value={selected}
  onChange={setSelected}
  options={[
    { value: "a", label: "Option A" },
    { value: "b", label: "Option B" },
  ]}
/>
```

#### Slider

| Prop (SingleValueSlider) | Type | Default |
|------|------|---------|
| `min` | `number` | **required** |
| `max` | `number` | **required** |
| `value` | `number` | — |
| `onChange` | `(value: number) => void` | — |
| `step` | `number` | `0.001` |
| `labeled` | `boolean` | — |
| `knobLabel` | `boolean` | — |
| `bare` | `boolean` | — |

Also exports `MultiValueSlider` (value/onChange use `number[]`).

#### Datepicker
Date/datetime input with calendar popover.

#### TreeSelect
Tree-shaped selection control in a searchable dropdown styled to match `Select`. Supports single- and multi-select, lazy-loaded branches, and the ARIA treeview keyboard pattern. Paths are `TreePath` (ordered arrays of raw node names) — no encoding needed even when names contain `/`.

| Prop | Type | Default |
|------|------|---------|
| `root` | `TreeNode` | **required** |
| `multiSelect` | `boolean` | `false` |
| `value` | `TreePath` (single) / `readonly TreePath[]` (multi) | — |
| `onChange` | `(path: TreePath \| null) => void` (single) / `(paths: TreePath[]) => void` (multi) | — |
| `leavesOnly` | `boolean` | `false` |
| `placeholder` | `string` | — |
| `anchor` | `SelectAnchor` | `SelectAnchor.BottomStart` |
| `portal` | `boolean` | `false` |
| `zIndex` | `ZIndex` | — |
| `panelMaxHeight` | `string` (CSS length) | `"18rem"` |
| `defaultExpanded` | `readonly TreePath[] \| boolean` | `false` |
| `displayValue` | `(path: TreePath, node: TreeNode) => string` | — |
| `loadChildren` | `(path: TreePath) => Promise<TreeNode[]>` | — |
| `disabled` | `boolean` | — |

```ts
type TreePath = readonly string[];
interface TreeNode {
  name: string;
  description?: string;
  can_select?: boolean;
  deprecated?: boolean;
  values?: TreeNode[]; // TreeNode[] = loaded branch; [] = lazy branch (triggers loadChildren); undefined = leaf
}
```

```tsx
const tree: TreeNode = {
  name: "vehicle_type",
  values: [
    { name: "car", values: [{ name: "Honda" }, { name: "Toyota" }] },
    { name: "motorcycle" },
  ],
};

<TreeSelect
  root={tree}
  value={value}
  onChange={(path) => setValue(path ?? undefined)}
  placeholder="Select a vehicle…"
/>
```

---

### Form Layout

#### FormField
Wraps any form control with label, description, and error display. **Always wrap form controls in FormField.**

| Prop | Type | Default |
|------|------|---------|
| `control` | `ReactNode` | **required** |
| `label` | `ReactNode` | — |
| `description` | `ReactNode` | — |
| `error` | `ReactNode` | — |
| `disabled` | `boolean` | — |
| `spacing` | `Spacing` | `Spacing.Sm` |

```tsx
<FormField
  label="Email"
  description="Your work email address"
  error={emailError}
  control={
    <Input
      value={email}
      onChange={(e) => setEmail(e.target.value)}
      error={!!emailError}
      type={InputType.Email}
    />
  }
/>
```

#### FormFieldGroup
Groups multiple FormFields with shared orientation and spacing.

| Prop | Type | Default |
|------|------|---------|
| `orientation` | `Orientation` | `Orientation.Column` |
| `spacing` | `Spacing` | `Spacing.Lg` |
| `disabled` | `boolean` | — |

```tsx
<FormFieldGroup orientation={Orientation.Column} spacing={Spacing.Lg}>
  <FormField label="First name" control={<Input />} />
  <FormField label="Last name" control={<Input />} />
</FormFieldGroup>
```

---

### Display

#### Icon

| Prop | Type | Default |
|------|------|---------|
| `name` | `IconName` | **required** |
| `size` | `Size` | — (fills parent) |
| `color` | `TextColor \| IconColor \| BrandColor` | — |

#### Pill
Small label/badge for status or metadata.

| Prop | Type | Default |
|------|------|---------|
| `size` | `Size` (Xs–Md only) | `Size.Sm` |
| `radius` | `Radius` | `Radius.Full` |
| `shadow` | `Shadow` | — |
| `color` | `TextColor` | `TextColor.Muted` |
| `backgroundColor` | `BackgroundColor \| SemanticColor \| StatusColor` | `BackgroundColor.Muted` |
| `icon` | `IconName` | — |
| `isStatus` | `boolean` | `false` |
| `onRemove` | `() => void` | — (renders a trailing close button) |

#### TextBadge
Inline text badge.

| Prop | Type | Default |
|------|------|---------|
| `color` | `TextColor \| IconColor` | `IconColor.Brand` |

#### Spinner
Loading indicator.

| Prop | Type | Default |
|------|------|---------|
| `size` | `Size` | `Size.Md` |

#### UnsetHint
Placeholder text shown when a value is unset.

| Prop | Type | Default |
|------|------|---------|
| `value` | `unknown` | — |
| `hint` | `string` | **required** |

---

### Lists

#### ListItem

| Prop | Type | Default |
|------|------|---------|
| `primaryContent` | `ReactNode` | — |
| `secondaryContent` | `ReactNode` | — |
| `actions` | `ReactNode` | — |
| `additionalContent` | `ReactNode` | — |
| `canSelect` | `boolean` | `false` |
| `selected` | `boolean` | `false` |
| `onSelected` | `(selected: boolean) => void` | — |
| `canDrag` | `boolean` | `false` |

#### RichList
Manages a list of ListItems with selection and drag-to-reorder.

| Prop | Type | Default |
|------|------|---------|
| `listItems` | `Descriptor<ListItemProps>[]` | **required** |
| `draggable` | `boolean` | `false` |
| `selected` | `string[]` | — |
| `onSelected` | `(selectedItems: string[]) => void` | — |
| `onOrderChange` | `(newItems: Descriptor<ListItemProps>[]) => void` | — |
| `spacing` | `Spacing` | `Spacing.Md` |

#### ImageList
Uniform (non-masonry) scrollable grid with optional infinite scroll. Vertical or horizontal scroll axis. Generic over the item data type `T`.

| Prop | Type | Default |
|------|------|---------|
| `items` | `Descriptor<T>[]` | **required** |
| `renderItem` | `(data: T, id: string) => ReactNode` | **required** |
| `cols` | `number` (columns if vertical, rows if horizontal) | `3` |
| `gap` | `number` (px) | `8` |
| `orientation` | `Orientation` | `Orientation.Column` |
| `rowHeight` | `number` (px, vertical) | `164` |
| `colWidth` | `number` (px, horizontal) | `164` |
| `onLoadMore` | `() => void` | — |
| `hasMore` | `boolean` | `false` |
| `loading` | `boolean` | `false` |

```tsx
<ImageList
  items={photos.map((p) => ({ id: p.id, data: p }))}
  renderItem={(photo) => <img src={photo.src} alt={photo.alt} className="size-full object-cover" />}
  cols={3}
  rowHeight={200}
  onLoadMore={fetchMore}
  hasMore={hasMore}
  loading={loading}
/>
```

---

### Tabs

#### ToggleSwitch
Tabbed content container with mutual exclusivity.

| Prop | Type | Default |
|------|------|---------|
| `tabs` | `Descriptor<ToggleSwitchTab>[]` | **required** |
| `variant` | `ToggleSwitchVariant` | `ToggleSwitchVariant.Default` |
| `size` | `Size` (Xs–Md only) | `Size.Sm` |
| `defaultIndex` | `number` | `0` |
| `onChange` | `(index: number) => void` | — |
| `fullWidth` | `boolean` | — |

```ts
enum ToggleSwitchVariant { Default, Soft, Full, Borderless }
interface ToggleSwitchTab { label: ReactNode; content: ReactNode }
```

```tsx
<ToggleSwitch
  variant={ToggleSwitchVariant.Soft}
  tabs={[
    { id: "tab1", data: { label: "Overview", content: <OverviewPanel /> } },
    { id: "tab2", data: { label: "Details", content: <DetailsPanel /> } },
  ]}
/>
```

---

### Feedback

#### Tooltip

| Prop | Type | Default |
|------|------|---------|
| `content` | `ReactNode` | **required** |
| `anchor` | `Anchor` (Top, Right, Bottom, Left only) | `Anchor.Top` |
| `shadow` | `Shadow` | `Shadow.Lg` |
| `portal` | `boolean` | `false` |

```tsx
<Tooltip content={<Text>Helpful tip</Text>}>
  <Button>Hover me</Button>
</Tooltip>
```

#### Toast
Notification banner with icon, title, description, and optional action.

| Prop | Type | Default |
|------|------|---------|
| `variant` | `Variant` (not Borderless) | `Variant.Primary` |
| `anchor` | `Anchor` | `Anchor.Bottom` |
| `open` | `boolean` | — |
| `icon` | `FC \| IconName` | — |
| `title` | `ReactNode` | — |
| `description` | `ReactNode` | — |
| `action` | `ReactNode` | — |

#### ActivityToast
Simplified toast for background activity.

| Prop | Type | Default |
|------|------|---------|
| `variant` | `Variant` | `Variant.Primary` |
| `anchor` | `Anchor` | `Anchor.BottomRight` |
| `open` | `boolean` | — |
| `icon` | `FC \| IconName` | — |
| `message` | `string` | — |

#### ToastContainer
Positioned container for toast elements.

| Prop | Type | Default |
|------|------|---------|
| `anchor` | `Anchor` | `Anchor.Bottom` |
| `open` | `boolean` | — |

---

### Table

Composable table primitives. All extend their native HTML element attributes. A `TableRow` with an `onClick` handler automatically gets a hover/pointer affordance.

```tsx
<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Status</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>Item 1</TableCell>
      <TableCell><Pill isStatus backgroundColor={StatusColor.Approved}>Active</Pill></TableCell>
    </TableRow>
  </TableBody>
</Table>
```

---

## Shared Types

```ts
/** Used by RichList, RichButtonGroup, ToggleSwitch, and Select for typed item collections. */
interface Descriptor<T> {
  id: string;
  data: T;
}
```

---

## Composition Patterns

### Form with validation
```tsx
<FormFieldGroup orientation={Orientation.Column} spacing={Spacing.Lg}>
  <FormField
    label="Name"
    control={<Input value={name} onChange={(e) => setName(e.target.value)} />}
  />
  <FormField
    label="Role"
    control={
      <Select
        exclusive
        value={role}
        onChange={(v) => setRole(v as string)}
        options={roleOptions}
      />
    }
  />
  <FormField
    label="Notifications"
    control={<Toggle checked={notify} onChange={setNotify} />}
  />
</FormFieldGroup>
```

### Card with header and action
```tsx
<Card>
  <Stack orientation={Orientation.Column} spacing={Spacing.Md}>
    <Stack align={Align.Center} justify={Justify.Between}>
      <Heading level={HeadingLevel.H3}>Settings</Heading>
      <Button variant={Variant.Secondary} size={Size.Sm}>Edit</Button>
    </Stack>
    <Text color={TextColor.Secondary}>Manage your preferences below.</Text>
  </Stack>
</Card>
```

### List with actions
```tsx
const items: Descriptor<ListItemProps>[] = data.map((item) => ({
  id: item.id,
  data: {
    primaryContent: item.name,
    secondaryContent: item.description,
    actions: (
      <Stack spacing={Spacing.Sm}>
        <Clickable><Icon name={IconName.Edit} size={Size.Sm} /></Clickable>
        <Clickable><Icon name={IconName.Delete} size={Size.Sm} /></Clickable>
      </Stack>
    ),
  },
}));

<RichList listItems={items} onSelected={setSelected} />
```

### Select with rich content
```tsx
const options: Descriptor<{ label: string; content?: ReactNode }>[] = items.map((item) => ({
  id: item.id,
  data: {
    label: item.name,
    content: (
      <Stack spacing={Spacing.Sm} align={Align.Center}>
        <Icon name={item.icon} size={Size.Sm} color={BrandColor.Accent} />
        <Stack orientation={Orientation.Column}>
          <Text variant={TextVariant.Label}>{item.name}</Text>
          <Text variant={TextVariant.Caption} color={TextColor.Secondary}>{item.hint}</Text>
        </Stack>
      </Stack>
    ),
  },
}));

<Select options={options} exclusive value={selected} onChange={setSelected} />
```

### Tree select in a form field
```tsx
<FormField
  label="Category"
  description="Pick the most specific node"
  control={
    <TreeSelect
      root={categoryTree}
      leavesOnly
      value={category}
      onChange={(path) => setCategory(path ?? undefined)}
      placeholder="Select a category…"
    />
  }
/>
```

### Row actions with a dropdown menu
```tsx
<Dropdown
  anchor={DropdownAnchor.BottomEnd}
  trigger={<Clickable><Icon name={IconName.MoreVertical} size={Size.Sm} /></Clickable>}
>
  <MenuIconTextItem icon={IconName.Edit} text="Rename" onClick={onRename} />
  <MenuIconTextItem icon={IconName.ContentCopy} text="Duplicate" onClick={onDuplicate} />
  <MenuSeparator />
  <MenuTextItem destructive onClick={onDelete}>Delete</MenuTextItem>
</Dropdown>
```

---

## Anti-Patterns

| Don't | Do instead |
|-------|-----------|
| `className="flex flex-col gap-4"` | `<Stack orientation={Orientation.Column} spacing={Spacing.Md}>` |
| `className="items-center"` on a Stack | `align={Align.Center}` prop |
| `className="justify-between"` on a Stack | `justify={Justify.Between}` prop |
| `size="md"` (string literal) | `size={Size.Md}` (enum) |
| Bare `<input>` or `<textarea>` | `<Input>` or `<TextArea>` wrapped in `<FormField>` |
| `<div onClick={...}>` for buttons | `<Button>` or `<Clickable>` |
| Custom spinner/loader | `<Spinner size={Size.Lg}>` |
| Raw `<table>` | `<Table>` / `<TableHeader>` / `<TableBody>` / `<TableRow>` / `<TableCell>` |
| Manual tooltip with hover state | `<Tooltip content={...}>` |
| `<div onContextMenu={...}>` custom right-click menu | `<ContextMenu menu={...}>` |
| Hand-rolled `<div>` dropdown with menu items | `<Dropdown trigger={...}>` + `Menu*` items |
| `className="z-[9999]"` / `style={{ zIndex: 9999 }}` | `zIndex={ZIndex.AboveModal}` prop |
| `style={{ transition: "all 200ms ease" }}` | `transitionPreset(TransitionPreset.All)` |
| Custom infinite-scroll image grid | `<ImageList onLoadMore={...} hasMore={...} />` |
