Skip to content

Dropdown

Dropdown binds a value to an option collection without exposing its internal interaction library.

import { useState } from 'react';
import { Dropdown } from '@cratis/components/Dropdown';
export function RolePicker() {
const [role, setRole] = useState<string>();
return (
<Dropdown
value={role}
options={[
{ label: 'Administrator', value: 'admin' },
{ label: 'Advisor', value: 'advisor' },
]}
onChange={setRole}
aria-label='Role'
/>
);
}

onChange receives the selected option’s value (here 'admin' or 'advisor'), not the option object and not a DOM event, so a state setter can be passed directly. The later examples on this page reuse role, setRole, and a roles array of the same shape.

When option objects contain label and value, those fields are used automatically. Use optionLabel and optionValue for another shape.

Choosing an option — with Enter on the keyboard-active option, or with a click — reports it through onChange once, closes the list, and shows that option. The Dropdown completes the selection itself, so a value that is not fed straight back, or that resolves to none of the options, never leaves the list hanging open over stale filter text. A value supplied afterwards still decides what is selected.

An external native label associates with the primary button, filter input, or multiple-select control through htmlFor and the Dropdown’s id:

<label htmlFor='project-role'>Project role</label>
<Dropdown id='project-role' value={role} options={roles} onChange={setRole} />

The single-select button announces the field label before its selected value (for example, “Project role Developer”), rather than announcing only “Developer.” Filter inputs and native multiple selects retain the field label as their accessible name. A label without an id receives a stable generated one after mounting; existing label IDs are preserved. Clicking the label still focuses the control. Independently inserted, replaced, or reassociated labels stay synchronized after mounting; generated IDs do not depend on a label’s position. A shared observer watches only potential label-association changes and disconnects when the last Dropdown in that DOM root unmounts.

Explicit aria-labelledby or aria-label props take precedence over automatic native-label discovery. For an accessible name in server-rendered HTML before hydration, provide the label reference explicitly:

<label id='project-role-label' htmlFor='project-role'>Project role</label>
<Dropdown
id='project-role'
aria-labelledby='project-role-label'
value={role}
options={roles}
onChange={setRole}
/>
<Dropdown
value={role}
options={roles}
filter
placeholder='Select a role'
filterPlaceholder='Search roles'
onChange={setRole}
aria-label='Role'
/>

Filtered selection follows the combobox pattern. A non-filtered single select follows the button/listbox pattern, so tests should query by accessible name rather than assume role="combobox" for both.

Set multiple and bind an array. Components uses native multiple selection when filtering is off and an accessible multi-value combobox when filter is enabled. Use a specialized collection picker when a large dataset needs virtualized search or chip collapsing.

Localizing show-options and clear-selection

Section titled “Localizing show-options and clear-selection”

The show-options trigger (the filtered combobox path) and the clear-selection action (showClear) carry their own accessible names, resolved with the same precedence everywhere Dropdown renders them: pt.trigger['aria-label'] (or pt.select['aria-label'] for the trigger) wins, then the CratisComponentsProvider’s messages.dropdown.showOptions / messages.dropdown.clearSelection, then the English defaults 'Show options' / 'Clear selection'. Configure them once for every Dropdown in the application through the provider, or per instance through pt.

<Dropdown
value={role}
options={roles}
filter
showClear
aria-label='Role'
pt={{ trigger: { 'aria-label': 'Show role options' } }}
/>
PropPurpose
valueSelected value or array for multiple selection.
optionsOption collection.
optionLabel / optionValueField names for object options.
placeholderEmpty trigger text.
filter / filterPlaceholderSearchable combobox mode.
multipleNative multiple-selection mode.
showClearClear action for a single selection.
invalid / disabledControl state.
className / styleClass and inline style for the outer wrapper.
id, name, tabIndex, aria-*Identity, form, and accessibility attributes.
onChange / onBlurSelection and focus callbacks.
ptCratis-owned stable part attributes.

root, input, select, trigger, value, clear, indicator, filter, popover, listbox, option, and multiple.

The popup is portaled outside modal clipping contexts and carries a z-index above Cratis dialogs. Styling uses the --cratis-* tokens and stable parts; no renderer selectors are required.