Skip to content
SuperCoach

Components · Inputs

Combobox

Beta

Searches and picks from a long list, such as the exercise library.

When to use

  • To pick one option from a long list where typing is faster than scrolling: an exercise, a food, a client, a program template.
  • When coaches know roughly what they're looking for and can type part of the name ("dead", "row").

When not to use

  • About 5 to 15 options: use a Select. It needs no typing and works the same on every device.
  • 2 to 4 options that should all be visible: use a Radio group.
  • To run actions or jump to pages: use a Command menu.
  • To browse the exercise library with filters, video previews and multi-select: that is the Exercise picker, which builds on this.

Anatomy

  1. 1. Label: always visible, above the trigger
  2. 2. Trigger: an outline button with role combobox, showing the chosen value or a placeholder
  3. 3. Chevron: shows that the trigger opens a list

A combobox is a composition, not a single component. It is a Popover whose content is a Command, as in shadcn's combobox recipe. Open the example above to see the popover's parts: a search field at the top, group headings ("Lower body"), options with a check on the chosen one, and an empty message when nothing matches. The popover is as wide as the trigger.

Variants

One style. Adapt it with what the list needs:

  • Grouped: CommandGroup with a heading, as in the example. Use groups only when coaches think in those groups (muscle area, meal type).
  • Single choice: choosing an option closes the popover and puts its label in the trigger. Choosing the same option again clears it.
  • Several choices: keep the popover open on select and show the choices as badges under the trigger. This is how the Exercise picker adds several exercises at once.

States

Choose an exercise for this block.

StateWhat changesToken
Hover (trigger)Accent fillbg-accent, text-accent-foreground
Focus-visible (trigger)Border and a 3px ringborder-ring, ring-ring/50
OpenPopover with border and shadow, on the popover layerbg-popover, shadow-md, z-(--z-popover)
Highlighted optionAccent fill, moved by arrows or the pointerbg-accent, text-accent-foreground
PlaceholderLighter text in the trigger and search fieldtext-muted-foreground
No resultsCentered empty messagetext-sm
Disabled (trigger or option)Half opacity, no pointer eventsopacity-(--opacity-disabled)
InvalidRed border and ring, error text belowborder-destructive, ring-destructive/20, text-destructive-text

Usage guidelines

No exercise matches. Check the spelling.
Do.An empty message that says nothing matched and what to try.
No results
Don't.A bare “No results”. It doesn't help the coach recover.
  • Match anywhere in the name, not only the start: "dead" finds "Romanian deadlift". cmdk does this by default.
  • Search the label the coach sees. Pass the visible label as the item's value, so English and Arabic names are both searchable in their own language.
  • Keep the chosen value visible in the trigger. Don't clear it when the popover opens.
  • Lists longer than a few hundred items should load results as the coach types, with a loading line in the list.

Content guidelines

  • Label: a short noun, sentence case: "Exercise", "Food", "Assign to client".
  • Trigger placeholder: an instruction, "Choose an exercise". Search placeholder: "Search exercises…", with an ellipsis.
  • Options: the name as coaches say it ("Barbell back squat"), not codes or IDs. Put detail such as equipment in a second line, not in brackets.
  • Empty message: say what happened and what to do: "No exercise matches. Check the spelling."
  • Arabic exercise names are the ones Gulf coaches use in the gym. Keep common English loan words such as "سكوات" where that is what coaches say.

Accessibility

KeyDoes
Enter, Space (on the trigger)Opens the popover and moves focus to the search field
TypeFilters the list; the first match is highlighted
Arrow down / upMoves the highlight through options, skipping disabled ones
Home / EndMoves to the first or last option
EnterChooses the highlighted option and closes the popover
EscapeCloses without changing the value; focus returns to the trigger
TabCloses the popover and moves on
  • The trigger is a button with role="combobox" and aria-expanded. It is labelled by the label and its current value (aria-labelledby lists both ids), so a screen reader announces "Exercise Romanian deadlift, combobox, collapsed".
  • The search field is a combobox with aria-autocomplete="list", controlling a listbox of options. Name the field with Command's label prop and the list with CommandList's label prop; without them, the field has no name and the list is called "Suggestions" in English, also on Arabic screens.
  • The highlighted option is set with aria-activedescendant, so focus stays in the search field while the screen reader reads each highlighted option: "Romanian deadlift".
  • aria-selected marks the highlighted option, not the chosen one; the chosen value is announced from the trigger.
  • The empty message is not a live region, so screen readers don't announce it on their own. Keep it short so it is quick to find with the reading cursor.

RTL

  • The chevron sits at the end of the trigger and the check at the end of each option (ms-auto), so both move to the left.
  • The popover aligns to the trigger's start edge (align="start"), which is its right edge in RTL.
  • The search icon stays at the start of the field, on the right.
  • Typing Arabic filters Arabic labels; Latin brand names inside Arabic labels stay left to right on their own.

Tokens used

Read from components/ui/command.tsx at build time, so this list changes when the component does. Opacity modifiers such as /90 are shadcn hover and focus tints on top of the token.

Semantic color tokens used by this component
TokenUtilitiesSuperCoachCoach
--accentbg-accentbrand-50orange-50
--accent-foregroundtext-accent-foregroundbrand-700orange-700
--borderbg-borderneutral-200neutral-200
--foregroundtext-foregroundneutral-950neutral-950
--muted-foregroundtext-muted-foregroundneutral-600neutral-600
--popoverbg-popoverwhitewhite
--popover-foregroundtext-popover-foregroundneutral-950neutral-950
  • Opacityopacity-(--opacity-disabled)
  • Radiusrounded-md
  • Radiusrounded-sm
  • Typefont-medium
  • Typetext-sm
  • Typetracking-wide
  • Typetext-xs

Popover

Read from components/ui/popover.tsx at build time, so this list changes when the component does. Opacity modifiers such as /90 are shadcn hover and focus tints on top of the token.

Semantic color tokens used by this component
TokenUtilitiesSuperCoachCoach
--muted-foregroundtext-muted-foregroundneutral-600neutral-600
--popoverbg-popoverwhitewhite
--popover-foregroundtext-popover-foregroundneutral-950neutral-950
  • Layerz-(--z-popover)
  • Radiusrounded-md
  • Shadowshadow-md
  • Typefont-medium
  • Typetext-sm

Props

The combobox has no component of its own: these are the Command parts it uses. See Popover for Popover, PopoverTrigger and PopoverContent.

<Command>

Also takes every prop of cmdk Command

No props of its own.

<CommandInput>

Also takes every prop of cmdk Command.Input

No props of its own.

<CommandList>

Also takes every prop of cmdk Command.List

No props of its own.

<CommandEmpty>

Also takes every prop of cmdk Command.Empty

No props of its own.

<CommandGroup>

Also takes every prop of cmdk Command.Group

No props of its own.

<CommandItem>

Also takes every prop of cmdk Command.Item

No props of its own.

Replaces in coach-admin

From the read-only audit of coach-admin: 53 style values, 71 uses.

coach-admin uses react-widgets (rw- classes: DropdownList, Multiselect) and small custom search inputs. Searchable lists become this combobox; short ones become a Select. The exercise library panel is documented separately as the Exercise picker.

Classes and selectors

  • .rw-widget-container17
  • .rw-widget-input13
  • .rw-widget-picker5
  • .rw-multiselect-tag4
  • .rw-btn4
  • .scpay-search__input4
  • .rw-popup-container3
  • .rw-dropdown-list-input2
  • .rw-dropdown-list2
  • .nutrition-select-container .SearchInput input2
  • .rw-multiselect > .rw-widget-picker ul2
  • .el-select__search-container2

SCSS variables in these styles

  • $scpay-control-heightnot a color: spacing, size or layout
  • $border-color→ --border
  • $scpay-control-bordernot a color: spacing, size or layout

Select, Command, Popover, Exercise picker, Form.