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

# UI Components

> Shared components every developer must use. Do not build inline equivalents.

## Overview

Matter Vault uses a system of shared components that enforce consistent design across the entire application. Every authenticated page, panel, modal, badge, and form field must use these components.

If a component doesn't support what you need, extend it — don't bypass it.

***

## Page Structure

### PageHeader

Used on every authenticated page. Renders the H1 title and gold rule divider. Never build a page header inline.

```tsx theme={null}
import { PageHeader } from "@/components/shared/page-header";

<PageHeader
  title={<>All <span className="text-gold italic">Matters</span></>}
  subtitle="Active and archived matters for this firm."
  size="lg"
  action={<Button variant="default" size="sm">+ New Matter</Button>}
/>
```

| Prop       | Type           | Default  | Notes                                                       |
| ---------- | -------------- | -------- | ----------------------------------------------------------- |
| `title`    | `ReactNode`    | required | Wrap emphasis word in `<span className="text-gold italic">` |
| `subtitle` | `ReactNode`    | —        | Plain prose below the title                                 |
| `action`   | `ReactNode`    | —        | Right-aligned button                                        |
| `size`     | `"lg" \| "md"` | `"lg"`   | `lg` = top-level pages, `md` = inner tabs                   |

<Note>Eyebrow labels are not used. The breadcrumb provides page context.</Note>

***

### PanelHeader

Used on every panel, table, or list section. Companion to PanelFooter.

```tsx theme={null}
import { PanelHeader } from "@/components/ui/panel-header";

<PanelHeader
  title="Documents"
  count={131}
  unit="file"
  actions={<Button variant="outline" size="sm">Export</Button>}
/>
```

| Prop      | Type                     | Notes                           |
| --------- | ------------------------ | ------------------------------- |
| `title`   | `ReactNode`              | Panel heading                   |
| `count`   | `number \| string`       | Shown after title in muted mono |
| `unit`    | `string`                 | Auto-pluralized                 |
| `actions` | `ReactNode`              | Right-aligned button cluster    |
| `density` | `"default" \| "compact"` | py-4 vs py-3                    |

***

### PanelFooter

Used on every panel with pagination or item counts.

```tsx theme={null}
import { PanelFooter } from "@/components/ui/panel-footer";

<PanelFooter
  showing={25}
  total={131}
  unit="item"
/>
```

***

## Forms

### Field

Wraps every form label + input pair. Handles label, required indicator, hint text, and error messages.

```tsx theme={null}
import { Field, FIELD_INPUT_CLASS } from "@/components/ui/field";

<Field label="First Name" htmlFor="first-name" required>
  <input
    id="first-name"
    autoFocus
    className={FIELD_INPUT_CLASS}
  />
</Field>
```

| Prop       | Type        | Notes                   |
| ---------- | ----------- | ----------------------- |
| `label`    | `string`    | Required                |
| `htmlFor`  | `string`    | Must match input id     |
| `required` | `boolean`   | Shows red asterisk      |
| `hint`     | `ReactNode` | Helper text below input |
| `error`    | `ReactNode` | Error message in red    |

### Input classes

Always import — never redeclare locally:

```tsx theme={null}
import {
  FIELD_INPUT_CLASS,           // standard height
  FIELD_INPUT_CLASS_COMPACT,   // h-9 compact variant
  FIELD_LABEL_CLASS,           // raw label class (prefer <Field>)
} from "@/components/ui/field";
```

***

### FieldRow

Used for display-mode label + value pairs in detail cards and sidebars.

```tsx theme={null}
import { FieldRow } from "@/components/dashboard/field-row";

<FieldRow label="Firm" value={me.firm.name} />
<FieldRow label="Matter Number" value="MV-2026-011" mono />
<FieldRow label="Notes" value={matter.notes} layout="stacked" />
```

***

## Modals

### Modal

All modals use this wrapper. Never use DialogContent directly.

```tsx theme={null}
import { Modal } from "@/components/ui/modal";

<Modal
  open={open}
  onOpenChange={setOpen}
  title="Add Contact"
  size="md"
  disableOutsideClick={true}
  footer={
    <>
      <button onClick={() => setOpen(false)} className={cancelClass}>
        Cancel
      </button>
      <Button
        variant="default"
        disabled={!isValid || isPending}
      >
        Save contact
      </Button>
    </>
  }
>
  <Field label="First Name" htmlFor="first-name" required>
    <input id="first-name" autoFocus className={FIELD_INPUT_CLASS} />
  </Field>
</Modal>
```

| Size | Max width   | Use for                        |
| ---- | ----------- | ------------------------------ |
| `sm` | `max-w-sm`  | Confirmation modals            |
| `md` | `max-w-lg`  | Standard form modals (default) |
| `lg` | `max-w-2xl` | Large form modals              |
| `xl` | `max-w-4xl` | Wizards and multi-step flows   |

**Enforced behaviors:**

* ESC always closes
* Click outside: blocked on form modals (`disableOutsideClick={true}`), allowed on read-only modals
* First input gets `autoFocus`
* Submit always has `disabled={!isValid || isPending}`
* No eyebrow text inside modals
* Always vertically centered

***

### ConfirmModal

Every destructive action must use this. Never show a delete button without confirmation.

```tsx theme={null}
import { ConfirmModal } from "@/components/ui/confirm-modal";

<ConfirmModal
  open={confirmOpen}
  onOpenChange={setConfirmOpen}
  title="Delete document?"
  description="This will permanently remove the document from this matter."
  confirmLabel="Delete document"
  onConfirm={handleDelete}
  isPending={isDeleting}
/>
```

The user must check "I understand this action cannot be undone" before the delete button enables.

***

## Badges

### Badge

All colored chips, status labels, and role badges use this component.

```tsx theme={null}
import { Badge } from "@/components/ui/badge";

<Badge tone="teal">Active</Badge>
<Badge tone="red">Legal Hold</Badge>
<Badge tone="gold">In Review</Badge>
<Badge tone="muted">Closed</Badge>
<Badge tone="blue">Medical Records</Badge>
<Badge tone="amber">Low Balance</Badge>
```

| Tone      | Use for                              |
| --------- | ------------------------------------ |
| `teal`    | Active, success, confirmed, clean    |
| `red`     | Error, critical, hold, adversarial   |
| `gold`    | Warning, pending, review, draft      |
| `amber`   | Caution, low balance                 |
| `muted`   | Closed, inactive, terminal stages    |
| `blue`    | Document categories, informational   |
| `neutral` | Custom labels with no semantic color |

***

## Empty States

### EmptyState

All empty panel states use this. Never build inline empty states.

```tsx theme={null}
import { EmptyState } from "@/components/ui/empty-state";

<EmptyState preset="no-documents" size="md" />
<EmptyState preset="no-contacts" size="sm" />
<EmptyState preset="no-activity" size="row" />
```

The `row` size renders a single italic line for in-panel one-liners.

***

## Banned Patterns

Never do these. If you see them in existing code, fix them.

| Pattern                                    | Use instead                                 |
| ------------------------------------------ | ------------------------------------------- |
| Local `INPUT_CLASS` / `LABEL_CLASS` consts | Import from `components/ui/field.tsx`       |
| Inline page headers                        | `<PageHeader>`                              |
| Eyebrow text inside modals                 | Remove — never use                          |
| `text-[9px]` or smaller                    | Minimum is `text-[10px]`                    |
| Hardcoded hex colors                       | Token classes only (`text-gold`, `bg-teal`) |
| `bg-raised` on inputs                      | `bg-surface` only                           |
| `variant="default"` on non-primary actions | `variant="outline"` or `variant="ghost"`    |
| Raw `<button>` for icon affordances        | `<Button variant="ghost" size="icon">`      |
| Delete without confirmation                | `<ConfirmModal>`                            |
| `DialogContent` directly                   | `<Modal>` wrapper                           |
| `font-semibold` on serif headings          | Remove — no-op on Cormorant Garamond        |
| Local badge color maps                     | `<Badge tone="...">`                        |
| `text-4xl` on inner tab pages              | `text-2xl` via `<PageHeader size="md">`     |
