Patterns
Collection
A collection is the list view — the page you'll live in most, and a view family (table · cards · board · …) built on two steady regions: a header + toolbar, and a scrolling data area. The one below is the real thing, in its table variant, snapped together from stock pieces — a PageHeader, a FilterBar in the toolbar slot, and the DataTable. Nothing custom, nothing fussy.
A list view
PageHeader up top (the title plus the primary action); the DataTable below with search, faceted filters, sortable and resizable columns, row selection, and pagination. Everything is wired to toasts — click a row, try the actions, run the whole loop. It all works.
| Tom Becker | Active | Dana Whitfield | Sam Cho | |||
| Erin Lattimore | Review | Marcus Reyes | Marcus Reyes | |||
| Hassan Ali | Active | Priya Anand | Dana Whitfield | |||
| Grace Nimoy | Inactive | Dana Whitfield | Lena Ortiz | |||
| Victor Pang | Active | Sam Cho | Sam Cho | |||
| Aisha Bello | Active | Lena Ortiz | Priya Anand | |||
| Owen Frye | Review | Marcus Reyes | Dana Whitfield | |||
| Maya Iverson | Active | Priya Anand | Sam Cho |
Anatomy
Four parts, stacked. The header tells you where you are, the toolbar narrows things down, the data is the work itself, and the states catch the edge cases. Every one is a piece you already have on the shelf.
- header
- A
PageHeader— the title and the primary action (New). The row count lives in the table footer, so the header doesn't repeat it. - toolbar
- Search (built into the DataTable) plus a
FilterBarof faceted, extensible filters, in the table's toolbar slot. - data
- The
DataTablein its table variant — sorting, selection + a bulk bar, column resize, pagination, row-click to the record. - states
- Loading skeleton, an empty state when filters match nothing, and an error boundary — the DataTable carries them.
View family
Table is the built variant and the default for compliance data. Cards, board, gallery, list and tree are future variants over the same two regions — the header + toolbar stay put; only the data region's renderer changes, picked by a view-switch in the toolbar (ADR-0028).
Usage
import {
PageHeader, DataTable, FilterBar, Badge, Button,
standardMetadataFilters, inSelectionFilter, dateRangeFilter,
} from '@halo-compliance/ui'
import { Plus } from 'lucide-react'
const [columnFilters, setColumnFilters] = useState([])
<PageHeader
eyebrow="Compliance"
title="Officers"
count={officers.length}
actions={<Button variant="primary" icon={<Plus />}>New officer</Button>}
/>
<DataTable
columns={columns} // status cell → <Badge tone={…} dot>
data={officers}
getRowId={(o) => o.id}
enableSelection
enableColumnResize
columnFilters={columnFilters}
onColumnFiltersChange={setColumnFilters}
onRowClick={(o) => navigate({ to: '/officers/$id', params: { id: o.id } })}
toolbar={
<FilterBar
filters={standardMetadataFilters({ statusOptions, users })}
columnFilters={columnFilters}
onColumnFiltersChange={setColumnFilters}
/>
}
/>When to use
- A set of records to scan, filter, and act on. Officers, reports, rulebooks — anything you browse before opening one.
- One primary action in the header. Create (New). Per-row and bulk actions live in the row and the selection bar, not the header.
- Filters in the toolbar, not the header. Search + faceted filters narrow the data; the header only orients and creates.
Related
- /page-header — the orientation band on top.
- /data-table — the grid, the FilterBar, and the page jumper in detail.
- /badge — the status column's badge: a solid color fill in this brand (the engraved plaque belongs to the proposed system).
- /record — where a row-click lands: the detail view.