Combobox

A combobox is a select with a search field inside the panel, so the reader can filter by typing instead of scrolling.

Both open in the same dropdown and store a value the same way.

Figma embed missing — the page this was adopted from shows a Figma frame here, of the dropdown open with a query typed into it. To be rebuilt by hand. There is no generated contact sheet to stand in for it either: no published Figma component set joins the name Combobox.

Choosing between a select and a combobox

Choose on one question: does the panel need a search field? The panel is the dropdown that opens when the reader clicks the trigger, and closes again when they pick a value or click away. Nothing else separates the two components. Single or multiple, grouped or flat, with icons or without — those are options on either, not reasons to pick one.

A search field is needed when the list holds a lot of items, or when its length is unknown at design time: apps, members, namespaces, tags. The test is whether the reader knows what they are looking for before the panel opens. Someone picking a severity is choosing from a menu. Someone picking a teammate already has a name in mind.

Count the list a customer would see, not the one in your demo environment. A seeded account has three apps and a handful of members. A real one can have hundreds, and the component is chosen for that.

Options

Everything below composes. Build the combination the screen needs rather than picking from a fixed set of variants.

OptionUse it whenSpecimen
MultipleThe reader stores several values in one fieldMultiple and Multiple With Selections
Group headersThe options fall into named sets the reader recognises — Pinned and Apps, Assigned and MemberFigma only
Leading iconThe icon identifies the option faster than its label, as an avatar does for a personFigma only
Trailing badgeLabels alone are ambiguous. Three apps named House of stroops are only tellable apart by a badgeFigma only
Helper text or a messageThe trigger's label does not say what the panel filtersFigma only

"Figma only" means the design library draws it and no Storybook story renders it. That is a gap in the specimen rather than proof the component cannot do it; ui/Select has a With Groups story and ui/Combobox does not, though both are drawn grouped. Check the component's props before assuming either way, and see gaps for what a missing specimen costs.

Single and multiple

Single and multiple are one component. ui/Combobox ships Multiple and Multiple With Selections rather than separate modules.

Selection controls sit on the left, and the single form marks its chosen row with a trailing checkmark. Both follow select.

Keep chosen values visible while a query is typed. A multiple combobox filters the list down as the reader types, which hides what they already picked.

⚠️ The product has two multi-select comboboxes. ui/Combobox has the stories above and one product use. MultiSelectCombobox is a second implementation under LogMetricFormOverlay, with three uses and no story. The library component is the one with a module, a story and this page. Which behaviour is correct is for whoever reconciles them, but the product should not carry both.

Group headers

Use group headers when the options fall into sets the reader already recognises, and leave the list flat when they do not. A header labels a run of options and is not selectable.

Keep header capitalisation consistent across the product. The design library currently mixes PINNED and APPS with Member and Assigned in the same family of menus, which is drift rather than two kinds of header.

Filtering applies to options, not to headers. A group whose options all fall out of the result set goes with them, rather than leaving an empty heading behind.

Badges on rows

Add a trailing badge, such as production or staging, when the label alone is ambiguous.

Do not badge every row. A badge on every row is a column, and it distinguishes nothing.

Helper text and messages

Two different things can sit below the search field, and they are not interchangeable.

A message carries more weight than an explanation needs, and spending it on ordinary text leaves nothing for a real problem.

Anatomy

PartDescription
TriggerButton showing the current value plus a chevron
Search fieldText field inside the dropdown, for filtering
Dropdown panelPopover container for the filtered results
Option rowText, an optional leading icon (avatar, star), an optional badge
Group header (optional)Section label — Pinned, Apps, Assigned, Member
Empty stateShown when no result matches the query — see empty state, user action kind
Helper text (optional)A short line below the search field clarifying the dropdown's purpose. Only when the label alone isn't enough

Figma naming

The working file draws this correctly and names the layers loosely. Before these frames are published, the layer names need to match the system's:

Figma layerShould beWhy
PillBadgeBadge is the construct, the contact sheet and the page. Pill joins nothing
Searchthe text field componentThe system has no Search component; the published sheet is text-field
Checkbox (on option rows)an option rowThese rows hold only text. The real checkbox rows are in the multiple select

Where it exists

Storybook — story ui-combobox--default (open it in Storybook). One of 3 stories. Drag the bottom edge to resize. ⚠️ The frame renders the component at the frame's own width, so in a narrow pane you are seeing a narrow rendering rather than the canonical one.
axisvalue
React moduleyes — shared UI library
React tierlibrary
CSS classnone
CSS filesnone
Storybookyes
Figmanot mapped
Uses in product1
Uses in admin0
Seen asjsx-tag
Join provenancenone

An empty cell above is a measurement, not an omission: every axis is printed whether or not it is populated. What each one costs is on /gaps.md, linked from the axis name wherever this page has a gap.

Members

A member is attached to its parent at module scope, so it is imported through the parent and versioned with it. Members have no page of their own: they declare no CSS class, carry no story and join no Figma set, and their uses are counted against the parent rather than separately.

None. Nothing is attached to this component as a member.