> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vertz.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Component Library

> Accessible, styled components — Button, Dialog, Select, Tabs, Table, Card, and more. Use these instead of building from scratch.

Vertz ships a full set of ready-to-use UI components with a shadcn-inspired theme. Use them instead of building from scratch.

## Architecture

The component system has two layers:

* **`@vertz/ui-primitives`** — Headless, accessible components with WAI-ARIA patterns. No styling, just behavior.
* **`@vertz/theme-shadcn`** — Wraps every primitive with shadcn-inspired styles, plus adds common components like Button, Input, Card, and Table.

Most apps only import from `@vertz/theme-shadcn`.

## Setup

Call `configureTheme()` once and register it with `registerTheme()`:

```tsx theme={null}
// src/styles/theme.ts
import { configureTheme } from '@vertz/theme-shadcn';
import { registerTheme } from '@vertz/ui';

const config = configureTheme({ palette: 'zinc', radius: 'md' });
registerTheme(config);

export const appTheme = config.theme;
export const themeGlobals = config.globals;
```

Then import components from `@vertz/ui/components` anywhere in your app:

```tsx theme={null}
import { Button, Input, Dialog, Select } from '@vertz/ui/components';
```

<Note>
  **Direct components** (`Button`, `Card`, etc.) are simple styled wrappers. **Primitive
  components** (`Dialog`, `Select`, etc.) are themed wrappers around `@vertz/ui-primitives` with
  compound sub-components (e.g., `Dialog.Title`, `Dialog.Footer`, `Select.Content`).
</Note>

***

## Form

### Button

Triggers an action or event. Supports intents and sizes.

```tsx theme={null}
import { Button } from '@vertz/ui/components';

<Button intent="primary" size="md">Save</Button>
<Button intent="outline" size="sm">Cancel</Button>
<Button intent="destructive" size="md">Delete</Button>
<Button intent="ghost" size="md">Ghost</Button>
```

Intents: `primary`, `secondary`, `outline`, `ghost`, `destructive`, `link`. Sizes: `sm`, `md`, `lg`.

### Input

Text input field for forms.

```tsx theme={null}
import { Input } from '@vertz/ui/components';

<Input placeholder="Enter your email..." />
<Input type="password" placeholder="Password" />
<Input disabled placeholder="Disabled" />
```

#### Per-input debounce

Add `debounce={ms}` to delay the form-level `onChange` callback for this input. Useful for search fields where you don't want to fire on every keystroke:

```tsx theme={null}
<form onChange={handleChange}>
  <Input name="q" debounce={300} placeholder="Search..." />
  <Select name="status">{/* fires immediately */}</Select>
</form>
```

See [Form-level onChange](/guides/ui/forms#form-level-onchange) for full details.

### Textarea

Multi-line text input.

```tsx theme={null}
import { Textarea } from '@vertz/ui/components';

<Textarea placeholder="Write a message..." />;
```

Supports `debounce={ms}` for form-level onChange, same as [Input](#per-input-debounce):

```tsx theme={null}
<Textarea name="notes" debounce={500} placeholder="Notes..." />
```

### Label

Accessible label for form controls.

```tsx theme={null}
import { Label } from '@vertz/ui/components';

<Label for="email">Email</Label>
<Input id="email" name="email" placeholder="you@example.com" />
```

### FormGroup

Groups form controls with error display.

```tsx theme={null}
import { FormGroup, Label, Input } from '@vertz/ui/components';

<FormGroup.FormGroup>
  <Label>Email</Label>
  <Input name="email" placeholder="Enter email" />
  <FormGroup.FormError>Please enter a valid email address</FormGroup.FormError>
</FormGroup.FormGroup>;
```

### Select

Dropdown selection control.

```tsx theme={null}
import { Select } from '@vertz/ui/components';

<Select defaultValue="Select a fruit...">
  <Select.Content>
    <Select.Group label="Fruits">
      <Select.Item value="apple">Apple</Select.Item>
      <Select.Item value="banana">Banana</Select.Item>
    </Select.Group>
    <Select.Separator />
    <Select.Group label="Vegetables">
      <Select.Item value="carrot">Carrot</Select.Item>
    </Select.Group>
  </Select.Content>
</Select>;
```

***

## Layout

### AppShell

Sidebar + content layout for SaaS apps. Includes themed navigation links with automatic active state.

```tsx theme={null}
import { AppShell } from '@vertz/ui/components';
import { Outlet } from '@vertz/ui';
import { Home, Settings, Users } from '@vertz/icons';

<AppShell>
  <AppShell.Sidebar>
    <AppShell.Brand>My App</AppShell.Brand>
    <AppShell.Nav>
      <AppShell.NavItem href="/dashboard" icon={Home}>
        Dashboard
      </AppShell.NavItem>
      <AppShell.NavItem href="/team" icon={Users}>
        Team
      </AppShell.NavItem>
      <AppShell.NavItem href="/settings" icon={Settings}>
        Settings
      </AppShell.NavItem>
    </AppShell.Nav>
    <AppShell.User>
      <span>Jane Doe</span>
    </AppShell.User>
  </AppShell.Sidebar>
  <AppShell.Content>
    <Outlet />
  </AppShell.Content>
</AppShell>;
```

**Sub-components:** `Sidebar`, `Brand`, `Nav`, `NavItem`, `Content`, `User`.

**NavItem** provides SPA navigation with active state detection:

* `match="prefix"` (default) — active when pathname starts with `href` (e.g., `/projects` matches `/projects/123`)
* `match="exact"` — active only on exact pathname match
* `icon` — accepts any `@vertz/icons` component, rendered at 16px

### Card

Container with header, content, and footer.

```tsx theme={null}
import { Card, Button } from '@vertz/ui/components';

<Card.Card>
  <Card.CardHeader>
    <Card.CardTitle>Card Title</Card.CardTitle>
    <Card.CardDescription>Card description goes here.</Card.CardDescription>
  </Card.CardHeader>
  <Card.CardContent>This is the card content area.</Card.CardContent>
  <Card.CardFooter>
    <Button intent="outline" size="sm">
      Cancel
    </Button>
    <Button intent="primary" size="sm">
      Save
    </Button>
  </Card.CardFooter>
</Card.Card>;
```

### Separator

Visual divider between content.

```tsx theme={null}
import { Separator } from '@vertz/ui/components';

<Separator />;
```

### Accordion

Expandable/collapsible content sections.

```tsx theme={null}
import { Accordion } from '@vertz/ui/components';

<Accordion>
  <Accordion.Item value="item-1">
    <Accordion.Trigger>Is it accessible?</Accordion.Trigger>
    <Accordion.Content>Yes. It adheres to WAI-ARIA patterns.</Accordion.Content>
  </Accordion.Item>
  <Accordion.Item value="item-2">
    <Accordion.Trigger>Is it styled?</Accordion.Trigger>
    <Accordion.Content>Yes. It matches the theme.</Accordion.Content>
  </Accordion.Item>
</Accordion>;
```

### Tabs

Tabbed content organization.

```tsx theme={null}
import { Tabs } from '@vertz/ui/components';

<Tabs defaultValue="account">
  <Tabs.List>
    <Tabs.Trigger value="account">Account</Tabs.Trigger>
    <Tabs.Trigger value="password">Password</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="account">Account settings here.</Tabs.Content>
  <Tabs.Content value="password">Password settings here.</Tabs.Content>
</Tabs>;
```

Supports `variant="line"` for an underline tab style.

***

## Data Display

### Badge

Small status or count indicator.

```tsx theme={null}
import { Badge } from '@vertz/ui/components';

<Badge color="blue">New</Badge>
<Badge color="green">Active</Badge>
<Badge color="red">Error</Badge>
<Badge color="yellow">Warning</Badge>
<Badge color="gray">Draft</Badge>
```

### Avatar

User profile image with fallback.

```tsx theme={null}
import { Avatar } from '@vertz/ui/components';

<Avatar.Avatar>
  <Avatar.AvatarFallback>JD</Avatar.AvatarFallback>
</Avatar.Avatar>;
```

### Table

Tabular data display.

```tsx theme={null}
import { Table } from '@vertz/ui/components';

<Table.Table>
  <Table.TableHeader>
    <Table.TableRow>
      <Table.TableHead>Name</Table.TableHead>
      <Table.TableHead>Status</Table.TableHead>
      <Table.TableHead>Role</Table.TableHead>
    </Table.TableRow>
  </Table.TableHeader>
  <Table.TableBody>
    <Table.TableRow>
      <Table.TableCell>Alice Johnson</Table.TableCell>
      <Table.TableCell>Active</Table.TableCell>
      <Table.TableCell>Admin</Table.TableCell>
    </Table.TableRow>
  </Table.TableBody>
</Table.Table>;
```

### Skeleton

Loading placeholder with built-in pulse animation. Comes with `Text` and `Circle` sub-components for common patterns.

```tsx theme={null}
import { Skeleton } from '@vertz/ui/components';

{/* Basic box skeleton */}
<Skeleton width="200px" height="16px" />

{/* Multi-line text skeleton (3 lines by default, last line shorter) */}
<Skeleton.Text />

{/* Custom line count and last-line width */}
<Skeleton.Text lines={5} lastLineWidth="50%" />

{/* Circular skeleton for avatars */}
<Skeleton.Circle />
<Skeleton.Circle size="48px" />
```

### EmptyState

Compound component for empty-data placeholders with icon, title, description, and action slots.

```tsx theme={null}
import { Button, EmptyState } from '@vertz/ui/components';

<EmptyState>
  <EmptyState.Icon>
    <InboxIcon />
  </EmptyState.Icon>
  <EmptyState.Title>No issues yet</EmptyState.Title>
  <EmptyState.Description>Create your first issue to get started.</EmptyState.Description>
  <EmptyState.Action>
    <Button intent="primary" size="sm">
      New Issue
    </Button>
  </EmptyState.Action>
</EmptyState>;
```

All slots are optional — use only what you need:

```tsx theme={null}
<EmptyState>
  <EmptyState.Title>No results</EmptyState.Title>
  <EmptyState.Description>Try adjusting your filters.</EmptyState.Description>
</EmptyState>
```

***

## Overlays

### Dialog

All dialogs use `useDialogStack()` with native `<dialog>` + `showModal()`. Dialog sub-components
(`Dialog.Header`, `Dialog.Title`, etc.) are used inside the dialog component.

```tsx theme={null}
import { useDialogStack } from '@vertz/ui';
import type { DialogHandle } from '@vertz/ui';
import { Button, Dialog } from '@vertz/ui/components';

function EditProfileDialog({ dialog }: { dialog: DialogHandle<void> }) {
  return (
    <>
      <Dialog.Header>
        <Dialog.Title>Edit profile</Dialog.Title>
        <Dialog.Description>Make changes to your profile here.</Dialog.Description>
      </Dialog.Header>
      <Dialog.Body>{/* Form fields go here */}</Dialog.Body>
      <Dialog.Footer>
        <Dialog.Cancel>Cancel</Dialog.Cancel>
        <Button intent="primary" size="md" onClick={() => dialog.close()}>
          Save changes
        </Button>
      </Dialog.Footer>
    </>
  );
}

// Open via DialogStack
const dialogs = useDialogStack();
await dialogs.open(EditProfileDialog, {});
```

### Confirm Dialog

Quick confirmation via `useDialogStack().confirm()`:

```tsx theme={null}
import { useDialogStack } from '@vertz/ui';
import { Button } from '@vertz/ui/components';

function DeleteButton() {
  const dialogs = useDialogStack();

  async function handleDelete() {
    const confirmed = await dialogs.confirm({
      title: 'Are you absolutely sure?',
      description: 'This action cannot be undone.',
      confirm: 'Continue',
      cancel: 'Cancel',
      intent: 'danger',
    });
    if (confirmed) {
      // perform delete
    }
  }

  return (
    <Button intent="destructive" size="md" onClick={handleDelete}>
      Delete Account
    </Button>
  );
}
```

### Sheet

Side panel that slides in from edge.

```tsx theme={null}
import { Button } from '@vertz/ui/components';
import { Sheet } from '@vertz/ui/components';

<Sheet>
  <Sheet.Trigger>
    <Button intent="outline" size="md">
      Open Sheet
    </Button>
  </Sheet.Trigger>
  <Sheet.Content>
    <Sheet.Title>Edit profile</Sheet.Title>
    <Sheet.Description>Make changes to your profile here.</Sheet.Description>
    {/* Content goes here */}
  </Sheet.Content>
</Sheet>;
```

Supports `side="left"`, `side="right"` (default), `side="top"`, `side="bottom"`.

### Popover

Floating content anchored to trigger.

```tsx theme={null}
import { Button } from '@vertz/ui/components';
import { Popover } from '@vertz/ui/components';

<Popover>
  <Popover.Trigger>
    <Button intent="outline" size="md">
      Open popover
    </Button>
  </Popover.Trigger>
  <Popover.Content>
    <div style="padding: 16px; width: 280px;">
      <h4>Dimensions</h4>
      <p>Set the dimensions for the layer.</p>
    </div>
  </Popover.Content>
</Popover>;
```

### Tooltip

Brief info on hover or focus.

```tsx theme={null}
import { Button } from '@vertz/ui/components';
import { Tooltip } from '@vertz/ui/components';

<Tooltip>
  <Tooltip.Trigger>
    <Button intent="outline" size="md">
      Hover me
    </Button>
  </Tooltip.Trigger>
  <Tooltip.Content>Add to library</Tooltip.Content>
</Tooltip>;
```

***

## Navigation

### Breadcrumb

Navigation breadcrumb trail.

```tsx theme={null}
import { Breadcrumb } from '@vertz/ui/components';

<Breadcrumb
  items={[
    { label: 'Home', href: '/' },
    { label: 'Projects', href: '/projects' },
    { label: 'Current Project' },
  ]}
/>;
```

The last item renders as the current page (no link). Supports a custom `separator` prop (defaults to `/`).

### Pagination

Page navigation controls.

```tsx theme={null}
import { Pagination } from '@vertz/ui/components';

<Pagination
  currentPage={2}
  totalPages={10}
  onPageChange={(page) => {
    /* navigate to page */
  }}
/>;
```

Supports `siblingCount` to control how many page numbers show around the current page.

### DropdownMenu

Menu triggered by a button click.

```tsx theme={null}
import { Button } from '@vertz/ui/components';
import { DropdownMenu } from '@vertz/ui/components';

<DropdownMenu>
  <DropdownMenu.Trigger>
    <Button intent="outline" size="md">
      Open
    </Button>
  </DropdownMenu.Trigger>
  <DropdownMenu.Content>
    <DropdownMenu.Label>My Account</DropdownMenu.Label>
    <DropdownMenu.Separator />
    <DropdownMenu.Item value="profile">Profile</DropdownMenu.Item>
    <DropdownMenu.Item value="billing">Billing</DropdownMenu.Item>
    <DropdownMenu.Separator />
    <DropdownMenu.Item value="logout">Log out</DropdownMenu.Item>
  </DropdownMenu.Content>
</DropdownMenu>;
```

***

## Feedback

### Alert

Inline alert messages.

```tsx theme={null}
import { Alert } from '@vertz/ui/components';

<Alert.Alert>
  <Alert.AlertTitle>Heads up!</Alert.AlertTitle>
  <Alert.AlertDescription>You can add components using the CLI.</Alert.AlertDescription>
</Alert.Alert>

<A.Alert variant="destructive">
  <Alert.AlertTitle>Error</Alert.AlertTitle>
  <Alert.AlertDescription>Your session has expired.</Alert.AlertDescription>
</Alert.Alert>
```

***

## Factory-based primitives

These components use an imperative factory API — they return DOM elements rather than JSX compound components. Import them from `@vertz/ui/components`.

<Note>
  These primitives haven't been converted to JSX compound components yet. They work by returning DOM
  elements that you embed in your JSX tree. JSX versions are planned.
</Note>

### Checkbox

Toggle control for boolean values.

```tsx theme={null}
import { Checkbox } from '@vertz/ui/components';

<label style="display: flex; align-items: center; gap: 8px">
  {Checkbox({ defaultChecked: true })}
  Accept terms and conditions
</label>;
```

### Switch

Toggle between on and off states.

```tsx theme={null}
import { Switch } from '@vertz/ui/components';

{
  Switch({ defaultChecked: true });
}
```

Supports `size: 'sm'` for compact variant.

### RadioGroup

Select one option from a set.

```tsx theme={null}
import { RadioGroup } from '@vertz/ui/components';

const radio = RadioGroup({ defaultValue: 'comfortable' });
radio.Item('default', 'Default');
radio.Item('comfortable', 'Comfortable');
radio.Item('compact', 'Compact');

// Use radio.root in your JSX
{
  radio.root;
}
```

### Slider

Range input with track and thumb.

```tsx theme={null}
import { Slider } from '@vertz/ui/components';

{
  Slider({ defaultValue: 50, min: 0, max: 100 }).root;
}
```

### Toggle

Toggle button with pressed state.

```tsx theme={null}
import { Toggle } from '@vertz/ui/components';

{
  Toggle();
}
```

### Progress

Shows task completion percentage.

```tsx theme={null}
import { Progress } from '@vertz/ui/components';

{
  Progress({ defaultValue: 66 }).root;
}
```

### Toast

Temporary notification popup.

```tsx theme={null}
import { Button } from '@vertz/ui/components';
import { Toast } from '@vertz/ui/components';

const t = Toast({});
document.body.appendChild(t.region);

<Button intent="outline" size="md" onClick={() => t.announce('Event has been created.')}>
  Show Toast
</Button>;
```

## Additional primitives

These components are available via `@vertz/ui/components`, but are less commonly used:

| Component      | Description                              |
| -------------- | ---------------------------------------- |
| Calendar       | Date grid with month navigation          |
| DatePicker     | Date picker composing Calendar + Popover |
| Carousel       | Slide navigation with prev/next controls |
| Collapsible    | Expandable/collapsible content section   |
| Command        | Searchable command palette               |
| ContextMenu    | Right-click context menu                 |
| Drawer         | Bottom/side panel wrapping Dialog        |
| HoverCard      | Hover-triggered interactive card         |
| Menubar        | Horizontal menu bar with dropdowns       |
| NavigationMenu | Site navigation with hover dropdowns     |
| ResizablePanel | Resizable panel layout with drag handles |
| ScrollArea     | Custom scrollbars                        |
| ToggleGroup    | Group of toggle buttons                  |

All are importable from `@vertz/ui/components` after registering a theme with `registerTheme()`.

***

## Headless usage

For custom styling, import directly from `@vertz/ui-primitives` and apply your own classes:

```tsx theme={null}
import { Dialog } from '@vertz/ui-primitives';

const dialog = Dialog.Root({ modal: true });
```

See [Styling](/guides/ui/styling) for details on `css()` and `variants()`.

## Theme configuration

`configureTheme()` accepts palette, radius, and color override options:

```tsx theme={null}
const config = configureTheme({
  palette: 'zinc', // zinc, slate, stone, gray, neutral
  radius: 'md', // none, sm, md, lg, full
  colors: {
    primary: { DEFAULT: 'oklch(0.55 0.2 260)', _dark: 'oklch(0.65 0.25 260)' },
    'primary-foreground': { DEFAULT: '#fff', _dark: '#fff' },
  },
});
```

The `colors` key deep-merges into the selected palette. Use `DEFAULT` for light mode and `_dark` for dark mode. You can also add custom token names.

* `config.theme` — Resolved CSS variables
* `config.globals` — Global CSS string to inject
* `config.styles` — Pre-built style objects for manual styling
* `config.components` — Used internally by `registerTheme()` — import components from `@vertz/ui/components` instead
