Skip to content

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.

import from "@na/ui/components/ViewSwitcher" ▶ Open in Storybook packages/ui/src/components/ViewSwitcher.tsx

When to use

  • Task / flow surfaces where switching between views earns its keep — /review history, 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 SegmentedControlrole="radiogroup", items as role="radio", arrow-key nav, aria-label.
  • The body slot is a plain <div>; the active view’s content carries its own roles.

▶ Open ViewSwitcher stories in Storybook