Patterns
Experience patterns
Patterns are compositions of Monarch components for screens most applications have. They carry layout, semantics and accessibility; data fetching, routing and state stay in the app. All are exported from
@rookcom/monarch-react.
| Pattern | Use it for | Typical screen |
|---|---|---|
PageHeader |
The top of every main view: one h1, context, page-level actions, optional tabs |
Project or pipeline views |
Toolbar + ToolbarGroup |
Controls that act on a list or board: search, view switch, density, export. role="toolbar", arrow-key roving focus |
Inbox, board |
FilterBar + FilterChip |
Active filters as removable chips with "Clear all" | Any filterable list |
Table + Pagination |
Sortable, selectable data tables | Lists and admin tables |
SplitLayout |
List + detail panes (stacked under md) |
Inbox, chat, documents |
SettingsSection + SettingsRow |
Titled settings groups with one control per row; tone="danger" for destructive zones |
Settings pages |
DescriptionList + DescriptionItem |
Key/value facts on detail pages | Record detail pages |
StatCard |
A single KPI with delta and period | Dashboards |
EmptyState / ErrorState / LoadingState |
The three non-happy states of any list or detail view | Every list or detail view |
Inbox
<PageHeader title="Inbox" actions={<Button variant="primary">New ticket</Button>} />
<Toolbar
label="Inbox tools"
search={<Input aria-label="Search tickets" placeholder="Search…" />}
end={<Button size="sm">Export</Button>}
filters={
<FilterBar onClearAll={clear}>
<FilterChip name="Status" value="Open" onRemove={() => remove('status')} />
</FilterBar>
}
>
<Button size="sm">Sort</Button>
</Toolbar>
<SplitLayout
listLabel="Tickets"
detailLabel="Ticket"
list={<TicketList />}
detail={selected ? <TicketDetail id={selected} /> : <EmptyState title="Select a ticket" />}
detailOpen={Boolean(selected)}
onBack={() => setSelected(null)}
/>
Data table
<Card flush>
<Table aria-label="Tasks" density="compact" stickyHeader>
<TableHeader>
<TableRow>
<TableSelectCell header label="Select all" checked={allChecked} onCheckedChange={toggleAll} />
<TableHead sort={sort.title} onSortChange={(d) => setSort({ title: d })}>Title</TableHead>
<TableHead numeric>Points</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{rows.map((r) => (
<TableRow key={r.id} selected={isSelected(r.id)}>
<TableSelectCell label={`Select ${r.title}`} checked={isSelected(r.id)} onCheckedChange={() => toggle(r.id)} />
<TableCell>{r.title}</TableCell>
<TableCell numeric>{r.points}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
<Pagination variant="compact" page={page} pageCount={pages} pageSize={50} totalItems={total} onPageChange={setPage} />
Settings (Workspace)
<SettingsSection title="Profile" description="What teammates see." actions={<Button variant="primary">Save</Button>}>
<SettingsRow label="Display name" required error={errors.name}>
<Input value={name} onChange={…} />
</SettingsRow>
<SettingsRow label="Weekly digest" description="Sent on Mondays.">
<Switch checked={digest} onCheckedChange={setDigest} />
</SettingsRow>
</SettingsSection>
<SettingsSection title="Delete workspace" tone="danger" description="This cannot be undone.">
<SettingsRow label="Delete">
<Button variant="danger">Delete workspace…</Button>
</SettingsRow>
</SettingsSection>
Dashboard
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 'var(--mn-space-4)' }}>
<StatCard label="Pipeline" value="₹12.4L" delta="+12%" deltaDirection="up" caption="vs last 30 days" />
<StatCard label="Churn" value="2.1%" delta="+0.3%" deltaDirection="up" deltaTone="danger" />
</div>
<DescriptionList columns={3}>
<DescriptionItem term="Owner">Ada Lovelace</DescriptionItem>
<DescriptionItem term="Stage">Proposal</DescriptionItem>
</DescriptionList>
Toasts
// root
<ToastProvider>{children}</ToastProvider>;
// anywhere
const { toast } = useToast();
toast({ title: 'Ticket archived', tone: 'success', action: { label: 'Undo', onClick: undo } });
Rules: success/info toasts are polite status messages; danger is an
alert. At most three are visible. Never put the only path to recover in a
toast; errors that block work belong in an Alert or ErrorState on the page.