# Dropdown

Level: molecule.

## Purpose and use

Present actions, one selection or a set of checkbox selections beside a trigger.
Use a filter wrapper for Apply/Clear drafts or searchable choices.

## Anatomy, behavior, and accessibility

- The `[wblDropdownTrigger]` slot anchors a CDK overlay. The panel contains menu
  rows with optional icons/images, side text, badges and separators, or a loader.
- With the default `controlled=false`, trigger activation toggles local open
  state and emits `openedChange`; backdrop click and overlay detach close it.
  With `controlled=true`, `opened` is the sole overlay-state source: trigger,
  backdrop/detach, Escape, and selection close interactions only emit the next
  requested value through `openedChange`. The overlay changes state only after
  the parent writes that value to `opened`; duplicate requests for the current
  parent value are not emitted. The component applies `aria-haspopup`,
  `aria-expanded`, and, when present, `aria-controls` to the native trigger.
  Default/right placement use viewport fallback positions; nontext trigger
  width and `minPanelWidth` bound panel width.
- Default/radio choices emit `itemSelect` and close; radio also emits the ID.
  Checkbox choices emit the next ID array and keep the menu open. The parent
  writes selection back. Empty selectedIds/null selectedId fall back to per-item
  `selected`; avoid conflicting sources when clearing a selection.
- Default menu rows use menuitem/menuitemcheckbox/menuitemradio semantics;
  `semanticRole='listbox'` uses options. Opening focuses the selected enabled
  row or first enabled row. ArrowUp/ArrowDown, Home/End, Escape, Enter and Space
  work for both overlay semantics; Escape and keyboard selection return focus to
  the native trigger. Disabled rows cannot select. Loading replaces rows and sets
  busy. Supply a native named trigger and clear, unique labels/IDs.
- The panel itself owns vertical scrolling with `overflow-y: auto` and
  `overscroll-behavior: contain`. Maximum heights are `26.75rem` for L,
  `19.875rem` for M, and `15.625rem` for S.

## Local component contract

This is the active owned contract for `component.dropdown`. The API below
is verified against the current local implementation, focused test and story.
The Source commit `0112cba0779a2d2a4b6c7a17243201694344d70a` remains
migration provenance, not the runtime owner.

### Classes and selectors

- `WblDropdownComponent` — `wbl-dropdown` (src/app/design-system/patterns/dropdown/dropdown.component.ts).

### Inputs

- `WblDropdownComponent.size = input<WblDropdownSize>('L')`
- `WblDropdownComponent.state = input<WblDropdownState>('default')`
- `WblDropdownComponent.itemsType = input<WblDropdownItemsType>('default')`
- `WblDropdownComponent.semanticRole = input<WblDropdownSemanticRole>('menu')`,
  where `WblDropdownSemanticRole = 'menu' | 'listbox'`
- `WblDropdownComponent.items = input<WblDropdownItem[]>([])`
- `WblDropdownComponent.opened = input(false)`
- `WblDropdownComponent.controlled = input(false)` — opt-in parent-controlled
  overlay mode; when true, the parent must update `opened` in response to
  `openedChange` for the requested state to take effect.
- `WblDropdownComponent.hasBackdrop = input(true)`
- `WblDropdownComponent.placement = input<WblDropdownPlacement>('default')`
- `WblDropdownComponent.panelId = input<string | null>(null)` — optional stable
  overlay id; when provided, the component binds it to the native trigger's
  `aria-controls`.
- `WblDropdownComponent.minPanelWidth = input<number | null>(null)`
- `WblDropdownComponent.selectedIds = input<readonly string[]>([])`
- `WblDropdownComponent.selectedId = input<string | null>(null)`

### Public types and allowed values

```ts
type WblDropdownSize = 'L' | 'M' | 'S';
type WblDropdownState = 'default' | 'loading';
type WblDropdownItemsType = 'default' | 'checkbox' | 'radio';
type WblDropdownSemanticRole = 'menu' | 'listbox';
type WblDropdownItem = {
  id: string;
  label: string;
  iconName?: WblIconName;
  imageSrc?: string;
  imageAlt?: string;
  badge?: string;
  rightText?: string;
  selected?: boolean;
  disabled?: boolean;
  separatorBefore?: boolean;
};
type WblDropdownPlacement = 'default' | 'right';
```

Icon-name inputs use `WblIconName` from the [shared icon contract](../atoms/icon.md);
choose a key present in the owned icon pack, not an arbitrary external icon name.

### Models

- None.

### Outputs

- `WblDropdownComponent.openedChange = output<boolean>()` — the local state
  change in default mode, or the requested next `opened` value in controlled
  mode.
- `WblDropdownComponent.itemSelect = output<WblDropdownItem>()`
- `WblDropdownComponent.selectedIdsChange = output<string[]>()`
- `WblDropdownComponent.selectedIdChange = output<string | null>()`

### Slots and projection markers

- `WblDropdownComponent: <ng-content select="[wblDropdownTrigger]">`

## Semantic role and keyboard behavior

- `semanticRole='menu'` is the default and preserves menu and menu-item
  semantics.
- `semanticRole='listbox'` is the narrow composition mode for selection fields.
  In checkbox mode the panel exposes `role="listbox"` with
  `aria-multiselectable="true"`; each enabled checkbox item is an
  `role="option"` with `aria-selected`. It does not expose a nested interactive
  checkbox to assistive technology.
- Opening focuses the selected enabled option or the first enabled row. In both
  modes ArrowUp/ArrowDown wrap through enabled rows; Home/End move to their
  bounds; Escape closes and restores focus; Enter/Space activate the row. This
  does not introduce draft selection, search, or a new selection data API.

### Local evidence

- Code: `src/app/design-system/patterns/dropdown/dropdown.component.ts`.
- Focused test: `src/app/design-system/patterns/dropdown/dropdown.component.spec.ts`.
- Public export: `export * from './patterns/dropdown/dropdown.component';` in `src/app/design-system/index.ts`.
- Storybook: `storybook/stories/dropdown.stories.ts` (`Design System/Patterns/wbl-dropdown`).

## Figma status

This runtime contract does not infer a Figma owner. Assembly evidence, when
verified, is maintained separately through the inventory-linked sidecar.

## M-002 scrollbar treatment

The Dropdown panel uses a native 4px scrollbar: `scrollbar-color` pairs the
tertiary thumb with a transparent track and `scrollbar-width: thin` provides the
fallback. WebKit uses `--space-100` width, a transparent track/corner, the
tertiary thumb, and `--radius-pill`. The thumb is visible whenever content
overflows; it is not hover-only. This styling adds no public API.

## Provenance

The archived source-document copy is historical evidence only. The active
resolver is local code/test/export and the exact Storybook story above.
