Dropdown
Primitive floating panel rendered through Angular CDK Overlay. Dropdown handles positioning, open/close animation, outside click, scroll-follow, and Escape close. It does not own selection or value state — Select, Combobox, Menu, or another host component provides that context.
Dropdown composes a panel component, a standalone trigger directive, and an option directive.
import { KuiDropdownComponent, KuiDropdownForDirective, KuiOptionDirective } from '@kikita-labs/ui';
// Import runtime styles once, application-wide:
import '@kikita-labs/ui/styles';Wire a standalone trigger with [kuiDropdownFor]. Prefer a native button so keyboard behavior and semantics are already correct.
<div class="standalone-dropdown-example">
<button type="button" [kuiDropdownFor]="menu">Actions</button>
<kui-dropdown #menu [maxHeight]="null">
<div kuiOption value="edit">Edit</div>
<div kuiOption value="delete" [disabled]="true">Delete</div>
</kui-dropdown>
</div>kui-field detects a nested dropdown, sets itself as the anchor, and toggles the panel when the field is clicked. This is the normal pattern behind input[kuiSelect].
<kui-field label="Fruit">
<input kuiSelect [(value)]="fruit" placeholder="Pick..." />
<kui-dropdown>
<div kuiOption value="apple">Apple</div>
<div kuiOption value="banana">Banana</div>
<div kuiOption value="cherry">Cherry</div>
</kui-dropdown>
</kui-field>panelWidth chooses a sizing strategy relative to the trigger; width overrides it entirely with an explicit CSS width.
<div class="panel-width-dropdown-example">
<button type="button" [kuiDropdownFor]="anchorPanel">panelWidth="anchor"</button>
<kui-dropdown #anchorPanel panelWidth="anchor">
<div kuiOption value="a">Matches trigger width</div>
</kui-dropdown>
<button type="button" [kuiDropdownFor]="contentPanel">panelWidth="content"</button>
<kui-dropdown #contentPanel panelWidth="content">
<div kuiOption value="b">Grows with a longer content line if needed</div>
</kui-dropdown>
<button type="button" [kuiDropdownFor]="explicitPanel">width="320px"</button>
<kui-dropdown #explicitPanel width="320px">
<div kuiOption value="c">Always exactly 320px wide</div>
</kui-dropdown>
</div>API verified against @kikita-labs/ui v1.6.1 public typings.
| Name | Type | Default | Description |
|---|---|---|---|
| maxHeight | string | null | '240px' | Preferred max height of the panel before it scrolls. Always additionally clamped to the viewport so the panel can never render taller than the screen. null removes only the preferred cap, not the viewport clamp. |
| offset | number | 4 | Gap in px between the anchor and the panel edge. |
| closeOnSelect | boolean (model) | true | Closes the panel when a selectable option is clicked. Two-way bindable via closeOnSelectChange. |
| panelRole | 'listbox' | 'dialog' | 'grid' | null | 'listbox' | ARIA role rendered on the panel. Set to dialog (or null to omit the role) for non-listbox projected content, e.g. kui-calendar. |
| panelWidth | 'anchor' | 'content' | 'auto' | 'anchor' | anchor matches the trigger's width exactly (listboxes). content grows with the panel's own content but never below the trigger's width. auto ignores the trigger's width and sizes purely to content. |
| width | string | null | null | Explicit panel width (any CSS width, e.g. 320px). Overrides panelWidth entirely when set. |
| isOpen | Signal<boolean> | - | Current open state. |
| open() | method | - | Shows the panel and attaches scroll/outside-click/Escape listeners. |
| close() | method | - | Starts the close animation and detaches listeners. |
| toggle() | method | - | Opens when closed, closes when open. |
| setAnchor(el) | method | - | Sets the anchor imperatively. Called by kui-field and [kuiDropdownFor]. |
| getPanel() | method | - | Returns the rendered panel element, if attached. |
| getPanelId() | method | - | Returns the stable panel id for ARIA wiring. |
| [kuiDropdownFor] | KuiDropdownComponent | - | Wires a standalone trigger to a dropdown instance and manages click toggling, aria-expanded, and aria-haspopup. Prefer a native button so keyboard behavior is already correct. |
| [kuiOption] value | unknown | - | Required. The value an option renders and emits. Set via the `value` input on `KuiOptionDirective`, e.g. `<div kuiOption value="edit">`. |
| [kuiOption] disabled | boolean | false | Disables click and keyboard selection for this option. |
| kuiOptionSelect | EventEmitter<unknown> | - | Emits the option value on selection. |
| --kui-dropdown-bg | CSS custom property | var(--kui-color-surface-elevated) | Panel background. |
| --kui-dropdown-border | CSS custom property | var(--kui-color-border) | Panel border color. |
| --kui-dropdown-radius | CSS custom property | var(--kui-radius-md) | Panel corner radius. |
| --kui-dropdown-shadow | CSS custom property | var(--kui-shadow-lg) | Panel drop shadow. |
| --kui-dropdown-viewport-margin | CSS custom property | var(--kui-space-6, 32px) | Margin subtracted from the viewport height when clamping the panel max-height. |
| Known non-feature | - | - | Dropdown does not own selection or value state itself. Select, Combobox, Menu, or another host component provides that context; a bare kui-dropdown only positions the panel and manages open/close. |
Dropdown owns overlay positioning and dismissal while keeping ARIA state on the trigger and options.
- Keep dropdown triggers native where possible, especially a plain button.
- Select-style hosts should expose
role="combobox",aria-expanded,aria-controls, andaria-describedbythrough their own directive; Dropdown itself only renders the panel role set bypanelRole(defaultlistbox). - Options render
role="option"inside the panel. Disabled options exposearia-disabled="true"and are skipped by selection. Escapecloses the panel.EnterandSpaceselect the focused option; by default (closeOnSelecttrue) selection also closes the panel.Tabcloses the panel without stealing focus back from the next tabbable element.- The panel closes itself if its anchor scrolls fully out of the viewport instead of following it off-screen or rendering detached.
- Dropdown covers keyboard dismissal, viewport-safe positioning, and static ARIA review. Full screen reader and other assistive-technology verification has not been completed for this primitive yet; see
state-coverage.mdin the library repository for current review status.