@ai-created/ui
All components

Fields & selection

Dropdown

An accessible controlled listbox for choosing one option in compact space.

DropdownDropdownPropsDropdownOption
View source

Component workbench

Configure the production component, inspect it at responsive widths, copy synchronized JSX, and share the exact state from one workspace.

Sort by

Anatomy

Named regions define what belongs to the component, what remains optional, and where semantic responsibility lives.

1

Listbox root

Required

Headless UI state and semantic owner.

2

Label

Required

Persistent visible listbox name.

3

Trigger

Required

Current selection or placeholder and disclosure icon.

button
4

Options panel

Required

Anchored, scrollable list of values.

5

Option

Required

Selectable row with optional check marker and disabled state.

Construction and authoring

The Figma-equivalent asset model, auto-layout, resizing, properties, slots, content bounds, localization, and governance contract.

Asset kind

component

Design library name

Components/Forms/Dropdown

Maturity

stable

Last reviewed

2026-08-29

Canvas applicability

Publish closed and open variants with nested option instances and selected state.

Auto layout

Direction
vertical
Gap
10px label gap; 5px trigger to menu
Padding
0px
Alignment
Stretch
Wrap
Menu options do not wrap by default
  • Measurements use the 20px playground and Figma reference root. The package does not set html font-size. Rem-based dimensions scale with the consumer root; literal px dimensions remain fixed.

Resizing

Width
fill
Height
hug
Minimum width
200px
Maximum width
Parent controlled
Minimum height
Content-derived: 92px with a single-line label and 57px trigger
Maximum height
Menu maximum 300px plus labelled control
Overflow
visible
  • Menu width matches the trigger and scrolls after its maximum height.
  • The Options slot anchors a floating Options panel; add rows inside that panel so label wrapping and menu growth preserve the 5px offset.

Exposed design properties

PropertyTypeDefaultCode mappingOptions or notes
LabelRequiredtextSort byRequired label; canvas example onlyNo additional constraint
Content previewRequiredvariantplaceholderWhether value resolves to a selected optionplaceholder, selected
Text (exposed Trigger)textSelect an optionplaceholder or selected option label; selected preview defaults to Most recentNo additional constraint
State previewvariantdefaultNative hover/focus-visible, disabled boolean and internal Headless UI open statedefault, hover, focus-visible, disabled, disabled-hover, open
OptionsRequiredslotNoneoptions; edit attached option rows inside Options panelNo additional constraint

Nested assets and slots

Listbox label

primitive / required

Accessible field label.

ListboxLabel

Trigger

subcomponent / required

Current value and chevron button.

ListboxButton

Options panel

subcomponent / required

Anchored, scrollable menu.

ListboxOptions

Option

subcomponent / required

Repeatable selected, active, or disabled row.

DropdownOption data item

Content limits

Options

Recommended two to fifteen options

Overflow: Use search or autocomplete for larger datasets.

A long unfiltered list is inefficient.

Option label

One line, recommended maximum 40 characters

Overflow: Truncate visually only when the full value is otherwise available.

Menu scanning depends on concise parallel labels.

Localization and RTL

Translatable: Label, Placeholder, Option labels

  • Text and chevron align to logical sides.
  • Menu anchors to logical start.
  • Test longest option.
  • Test a translated placeholder.
  • Test keyboard navigation in right-to-left mode.

Responsive behavior

Trigger fills its form column; menu matches trigger width.

No internal breakpoint

  • Keep the menu inside the viewport.
  • Use a full-screen selection pattern only in a product-level mobile adaptation.

Authoring limits

  • Figma open state does not validate focus movement, collision handling, or scrolling.
  • Options use stable untranslated values and localized labels.

Accountable owner

Design systems form foundations owner

Canonical source: src/components/Dropdown.tsx

  • Selection behavior changes require Headless UI parity and keyboard testing.
  • A breaking property, token, or behavior change requires design and engineering approval, a migration note, and a major release.
  • The design asset, implementation, documentation, accessibility contract, and regression coverage must ship in the same change.
Read the system construction standard

Visual specification

These measurements and semantic tokens define the supported visual contract. Local overrides should preserve the same hierarchy and interaction cues.

Label-to-trigger gap

10px

Trigger padding

20px left / 50px right / 15px vertical

Option padding

50px left / 20px right / 15px vertical

Radius

6px

Panel maximum height

300px

Panel offset

5px

Icons

20 × 20px

Rules

  • Keep option labels on one line when possible; rows truncate overflow.
  • The panel width follows the trigger.
  • Use the placeholder only before a meaningful value is selected.
  • Measurements use the 20px playground and Figma reference root. The package does not set html font-size. Rem-based dimensions scale with the consumer root; literal px dimensions remain fixed.

Token dependencies

--color-surface2

Trigger and active option background.

--color-surface

Options panel.

--color-control-border / --color-control-border-strong

Trigger resting and hover boundaries.

--color-border

Options panel border.

--color-focus

Trigger focus outline.

--color-accent

Selected check marker.

--shadow-elevation-medium / --layer-dropdown

Options elevation and stacking.

States and behavior

A component is not complete until its passive, interactive, disabled, and exceptional states remain understandable in both themes.

closed

Trigger
Listbox is idle.
Visual response
Label and trigger show the current value or placeholder.
Behavior
Activation opens the options panel.

open

Trigger
Trigger is activated.
Visual response
Anchored options panel appears below the trigger.
Behavior
Focus and keyboard navigation move through options.
Accessibility
Headless UI exposes expanded and listbox relationships.

selected

Trigger
Option value matches value.
Visual response
Primary text, medium weight, and accent check.
Behavior
Choosing another option calls onChange and closes.

disabled

Trigger
Group or option is disabled.
Visual response
50% opacity and not-allowed cursor.
Behavior
Disabled trigger cannot open; disabled option cannot select.

Accessibility contract

Semantics, accessible naming, keyboard behavior, focus and announcements are implementation requirements, not optional documentation.

Semantics
Headless UI Listbox with labelled button, options, and managed focus.
Accessible name
Required visible label names the listbox.
  • Use stable unique option values.
  • Provide a selected value that exists in options or intentionally show the placeholder.
  • Disabled options must not be the only way to explain unavailable choices.
  • Do not replace the listbox with a native select when rich behavior is unnecessary without considering platform benefits.

Keyboard behavior

Enter / Space / Arrow Down
Opens the listbox and enters option navigation.
Arrow Up / Arrow Down
Moves the active option.
Enter / Space
Selects the active option.
Escape
Closes without changing selection.
Home / End
Moves to the first / last enabled option.
Typeahead
Moves to an option matching typed characters.

Implementation

Use the public package API and preserve the controlled-state, native-attribute, and ref contracts described below.

tsx
import { Dropdown, type DropdownOption } from '@ai-created/ui';

Client Component required

  • Generic string values preserve domain types.
  • The trigger button ref is forwarded.
  • Headless UI owns option roles, focus, and dismissal.
  • An Enter handler normalizes trigger opening behavior.
tsx
<Dropdown options={options} value={sort} onChange={setSort} label="Sort by" />

Public API

These props and helpers are compatibility contracts. Changes require migration guidance and release notes.

PropTypeDefaultContract
optionsDropdownOption<T>[]requiredOptions with value, label, and optional disabled.
valueTrequiredControlled selected value.
onChange(value: T) => voidrequiredCalled on selection.
labelstringrequiredListbox label.
placeholderstringSelect an optionFallback text.
disabledbooleanfalseDisables the listbox.

Usage guidance

These rules keep the component recognizable across products without preventing deliberate composition.

Do

  • Use for a moderate set of short, known options.
  • Sort or group options in a predictable domain order.

Do not

  • Do not use for two or three choices that should stay visible.
  • Do not use for large datasets that require search or virtualization.

Testing contract

The component should be verified as rendered UI, an interaction model, an accessibility surface, and a theme-aware visual artifact.

Unit

  • Resolves selected label and placeholder and forwards trigger ref.
  • Calls onChange with the selected enabled value.

Interaction

  • Open, arrow, select, escape, outside dismissal, typeahead, and disabled flows work.

Accessibility

  • Label, expanded state, active descendant/option semantics, and focus restoration are valid.

Visual

  • Capture closed/open, placeholder, selected, active, disabled option/trigger, overflow, themes, and top alignment beside a Field with an equal-height label.

Related specifications