ViewSwitcher
<ViewSwitcher> is the canonical “rotate between renderings of the same data” surface — List / Grid / Board / Calendar. Composes SegmentedControl for the toggle + a body slot for the active view.
When to use
- Task / flow surfaces where switching between views earns its keep —
/reviewhistory, deal pipeline (table vs. board), inbox. - Anywhere the underlying query is the same and only the render changes.
Don’t use ViewSwitcher for: registries / audit logs (scan-only — pick the best view and stick to it), sub-pages of an entity (use Tabs), or apparent-only-on-mobile alternatives.
Default
AcmeNegotiation
GlobexDiscovery
InitechClosed-Won
<ViewSwitcher ariaLabel="View" defaultView="list" views={[ { id: 'list', label: 'List', icon: IconList, render: <ReviewTable rows={items} /> }, { id: 'grid', label: 'Grid', icon: IconLayoutGrid, render: <ReviewGrid rows={items} /> }, { id: 'board', label: 'Board', icon: IconLayoutKanban, render: <ReviewBoard rows={items} /> }, ]}/>API
| Prop | Type | Default | Description |
|---|---|---|---|
views * | ViewDescriptor[] | — | Each: { id, label, icon?, count?, render }. |
defaultView | string | — | Initial active view id (uncontrolled). |
activeView | string | — | Controlled active view id. Pair with onViewChange. |
onViewChange | (viewId: string) => void | — | Fires when the user picks a different view. |
searchParamKey | string | null | "view" | URL param to persist the active view. null disables URL persistence. |
actions | ReactNode | — | Right-aligned actions next to the segmented control. |
ariaLabel | string | "View" | Accessible label for the radiogroup. |
Design guidelines
✓ Do
- Persist the active view via the URL param so reload + share preserves the user's choice.
- Order views by frequency — what the user picks 80% of the time goes leftmost.
- Show counts in the segmented control when they help the user pre-select ("Inbox 12").
✗ Don't
- Use ViewSwitcher when the views display different data. That's sub-pages — use Tabs or routes.
- Render 6+ views. The segmented control gets unreadable past 4–5.
- Hide the active body when no view is active. Default to views[0].
Accessibility
- Inherits
SegmentedControl—role="radiogroup", items asrole="radio", arrow-key nav,aria-label. - The body slot is a plain
<div>; the active view’s content carries its own roles.
Related
SegmentedControl— The underlying primitive.Tabs— When the views display different data.DataTable,BoardView— Common content for ViewSwitcher views.