Switcher
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.
| Switcher | Select | |
|---|---|---|
| Typical use | App/context navigation | Form fields, filters, settings |
| Trigger | Tertiary Switcher.Trigger | Form-styled Select.Button |
| Label | aria-label on Switcher.Root | Select.Label |
| Popover | Built into Switcher.List | Explicit Select.Popover |
| Selection API | value, defaultValue, onChange | value, defaultValue, onChange |
| Multiple selection | No (single only) | Yes (selectionMode="multiple") |
| Form support | No (name, validation, isRequired, etc. are omitted) | Yes (Select.Error, isInvalid, native forms) |
| Built-in layouts | layout="stack" or layout="grid" on Switcher.List | Manual list layout |
| Search | Optional 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. Providearia-label(oraria-labelledby) onSwitcher.Root. - Not a form control — Do not use Switcher for form submission. It does not support
name,isRequired,isInvalid, or validation props. UseSelectfor form fields. - Selection prop names — Use
value/defaultValue/onChange. Deprecated React Aria aliases such asselectedKey,defaultSelectedKey, andonSelectionChangeare not exposed. - Composition —
Switcher.Listrenders the popover internally. Do not addSelect.Popoveror a separate popover wrapper. - Popover height — Pass
maxHeighttoSwitcher.Listto override the popover’s default 500px maximum height. - Search — Pass a
filterfunction fromuseFiltertoSwitcher.List. SetsearchPlaceholder(and optionallysearchAriaLabel) when search is enabled. - Rich list items — For non-plain-text children, set
textValueonSwitcher.Itemso selection, filtering, and screen readers work correctly. - Default width —
Switcher.Rootapplies amin-widthso stack-layout popovers are at least as wide as the trigger and may grow to fit wider list content. Override withstyleorclassNameif your layout needs a narrower trigger. - Grid layout — With
layout="grid",Switcher.Listuses the sameListBoxgrid asListBox.Root.gridColumnsdefaults to 2. Override--list-box-grid-column-widthon the list with thestyleprop to change column width. The popover is at least as wide as the trigger and grows to fit the grid.