Skip to content

Switcher

Github icon-storybook-square Storybook Markdown Copy for LLM

A simplified single-select switcher for navigating between apps or contexts. It uses a tertiary button trigger with an implicit popover, and supports stack or grid list layouts with optional search.

Import

import { Switcher } from "@cfa/react-core";

Live Editor

Guidelines

When to use

  • Switcher is for changing the active product context, such as apps, locations, or languages, which changes what the product shows.

Do

  • Use Switcher instead of Select when the choice lives in app chrome, such as the header or side navigation, and is not a form field.
  • Show the current selection in the trigger. Include the leading icon when list items use icons.
  • Start with label-only options when names are self-explanatory. Add icons, descriptions, or a secondary label, such as a store ID, as options need disambiguation.
  • Enable search when the list is too long to scan quickly.
  • Keep option labels short and parallel. Use sentence case, such as Operator portal, Team member hub, and Supply chain.

Avoid

  • Mixing action verbs or long dashboard-style names in Switcher, such as View team schedules or Supply chain management dashboard.
  • Using Switcher to collect a value in a form. Use Select, Combobox, or RadioGroup.
  • Using Switcher for command or action lists. Use Menu.
  • Using Switcher for peer content panels on the same page. Use Tabs.
  • Using Switcher for on/off or persistent view-mode toggles. Use Switch or ToggleButton.

Composition

A full app switcher with rich list items (icons and descriptions), a two-column grid layout, and search. Set textValue on items with non-plain-text children so selection, filtering, and screen readers work correctly.

Storybook

More examples, including trigger sizes, grid layouts, and disabled states, are available in Storybook.

Switcher vs Select

Use Switcher when the user is switching between apps, workspaces, or contexts — typically in a header or shell, not inside a form field.

Use Select when the user is choosing a value as part of a form or settings flow.

SwitcherSelect
Typical useApp/context navigationForm fields, filters, settings
TriggerTertiary Switcher.TriggerForm-styled Select.Button
Labelaria-label on Switcher.RootSelect.Label
PopoverBuilt into Switcher.ListExplicit Select.Popover
Selection APIvalue, defaultValue, onChangevalue, defaultValue, onChange
Multiple selectionNo (single only)Yes (selectionMode="multiple")
Form supportNo (name, validation, isRequired, etc. are omitted)Yes (Select.Error, isInvalid, native forms)
Built-in layoutslayout="stack" or layout="grid" on Switcher.ListManual list layout
SearchOptional filter on Switcher.List (with useFilter)Use Combobox or Autocomplete instead

If you need sections, a clear button, invalid/required states, or multiple selection, use Select (or Combobox for typeahead). If you need a compact app switcher with optional grid or search, use Switcher.

Gotchas

  • Accessible name — There is no Switcher.Label. Provide aria-label (or aria-labelledby) on Switcher.Root.
  • Not a form control — Do not use Switcher for form submission. It does not support name, isRequired, isInvalid, or validation props. Use Select for form fields.
  • Selection prop names — Use value / defaultValue / onChange. Deprecated React Aria aliases such as selectedKey, defaultSelectedKey, and onSelectionChange are not exposed.
  • Composition — Switcher.List renders the popover internally. Do not add Select.Popover or a separate popover wrapper.
  • Popover height — Pass maxHeight to Switcher.List to override the popover’s default 500px maximum height.
  • Search — Pass a filter function from useFilter to Switcher.List. Set searchPlaceholder (and optionally searchAriaLabel) when search is enabled.
  • Rich list items — For non-plain-text children, set textValue on Switcher.Item so selection, filtering, and screen readers work correctly.
  • Default width — Switcher.Root applies a min-width so stack-layout popovers are at least as wide as the trigger and may grow to fit wider list content. Override with style or className if your layout needs a narrower trigger.
  • Grid layout — With layout="grid", Switcher.List uses the same ListBox grid as ListBox.Root. gridColumns defaults to 2. Override --list-box-grid-column-width on the list with the style prop to change column width. The popover is at least as wide as the trigger and grows to fit the grid.