List dropdown
A borderless text trigger that opens a short list and shows the chosen value.
import { ListDropdown } from "@/ui";ListDropdown is a small wrapper around the dropdown menu. The trigger is plain 12px text with a 12px caret and no border, so it can sit inside a header or a line of text without looking like a form field. The list is the product dropdown, with a blue check on the chosen option.
Use it for a quiet choice inside a panel header, such as which version of a plan you are looking at. For a choice in a form, use Dropdown. For a menu of actions, use the dropdown menu directly.
Usage
Pass the options, the current value and onChange. The trigger shows the label of the option whose value matches.
const [version, setVersion] = useState(3);
<ListDropdown
options={[
{ value: 1, label: "v1" },
{ value: 2, label: "v2" },
{ value: 3, label: "v3 · Latest" },
]}
value={version}
onChange={setVersion}
/>Custom trigger text
renderLabel receives the selected option and returns what the trigger shows. Use it to name the setting next to its value. The option is undefined when nothing matches value, so handle that case.
<ListDropdown
options={sorts}
value={sort}
onChange={setSort}
renderLabel={(o) => `Sort by: ${o?.label ?? "None"}`}
/>Long lists
The list is capped at 280px and scrolls after that. If people need to search a list this long, use a field with search instead.
<ListDropdown options={campaigns} value={campaign} onChange={setCampaign} />States
<ListDropdown options={[{ value: 1, label: "v1 · Latest" }]} value={1} disabled />
<ListDropdown options={sorts} value={undefined} onChange={setSort} renderLabel={(o) => o?.label ?? "Choose a sort"} />| State | Look |
|---|---|
| Default | 12px --color-text-primary, 6px by 12px padding, 8px corners, no background. |
| Hover | --color-grey-50 background. |
| Open | The panel opens below, lined up with the trigger's left edge, 4px away. The chosen option has a bold 16px check in --color-primary-500. |
| Disabled | Text and caret in --color-grey-300. The trigger cannot be clicked or focused into opening. The workbook disables it while a plan has only one version. |
| No match | With no renderLabel, the trigger shows only the caret. Always give a renderLabel fallback when value can be empty. |
| No options | The trigger still renders, but nothing opens. |
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
options | Array<{ value: string | number, label: string }> | [] | value is compared with ===. |
value | string | number | The selected option's value. | |
onChange | (value) => void | Called with the chosen option's value, also when it is already selected. | |
renderLabel | (option | undefined) => ReactNode | Replaces the trigger text. | |
disabled | boolean | false | |
className | string | Goes on the wrapper <div>, not the trigger. For layout only. |
No other props are passed on. In particular there is no aria-label, align or placeholder prop.
Accessibility
- The trigger is a native
<button>and the list is the product dropdown, so Enter, Space and the arrow keys open it, the arrow keys move through options, Escape closes it and focus returns to the trigger. See the dropdown menu for the full list. - The trigger's accessible name is only the visible text, such as "v3 · Latest". Because there is no
aria-labelprop, userenderLabelto put the setting's name in the text ("Sort by: Pipeline") when the value alone does not explain itself. - The chosen option is shown with a drawn check, not
aria-checked, so a screen reader does not hear which option is selected. The trigger text is the only place it is announced.
The component's CSS still has rules for its own older menu (.list-dropdown__menu, .list-dropdown__item). They are no longer used since the list moved to the product dropdown, and can be deleted.
Do and don't
- Do keep it for short lists of plain text options, up to about ten.
- Do make the trigger text say what is being chosen, with
renderLabelif needed. - Don't use it in a form. Form fields need a visible label and a border; use Dropdown.
- Don't use it for actions. It shows a chosen value, so every option should be a value, not a verb.