1. Kikita UI
  2. Components
  3. Surfaces
  4. Dropdown
Skip to content
Kikita UI
  • Installation
  • Theming
  • Tokens
  • Typography
  • Density
  • Accessibility
  • Migrating to 2.0
  • Internationalization
  • Defaults
  • Structural icons
  • Auto Focus
  • Forms
  • AI Support
  • Package Smoke
  • Button
  • Link
  • Icon Button
  • Menu
  • Command Palette
  • Field
  • Input
  • Color Input
  • Select
  • Combobox
  • Date Picker
  • Time Picker
  • Calendar
  • Calendar Range
  • File Upload
  • Segmented
  • Slider
  • Number Input
  • OTP Input
  • Textarea
  • Checkbox
  • Switch
  • Radio
  • Group
  • Badge
  • Loader
  • Skeleton
  • Tooltip
  • Toast
  • Empty State
  • Alert
  • Progress
  • Card
  • Tabs
  • Accordion
  • Breadcrumbs
  • Popover
  • Dialog
  • Media Viewer
  • Drawer
  • Splitter
  • Dropdown
  • Separator
  • Stepper
  • Icon
  • Avatar
  • Table
  • Pagination
  • Line Chart
  • Bar Chart
  • Scatter Chart
  • Donut Chart
  • Carousel
  • Chip
  • Scrollbar
  • Tree
  • Import
  • Usage
  • Controlled open state
  • Inside kui-field
  • Panel width
  • API
  • Accessibility
  • Migrating to 2.0
  • Provider defaults
  • Theming tokens

Surfaces

Stable - @kikita-labs/ui v2.0.0

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.

Import

Dropdown composes a panel component, a standalone trigger directive, and an option directive.

dropdown.ts
import { KuiDropdown, KuiDropdownFor, KuiOption } from '@kikita-labs/ui';

// Import runtime styles once, application-wide:
import '@kikita-labs/ui/styles';

Usage

Open in Playground

Wire a standalone trigger with [kuiDropdownFor]. Prefer a native button so keyboard behavior and semantics are already correct.

<div class="standalone-dropdown-example">
  <button kuiButton 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>

Controlled open state

Bind [(open)] when the parent owns visibility. Dropdown keeps the signal synchronized when it closes because of Escape, outside interaction, or an off-screen anchor.

<div class="controlled-open-dropdown-example">
  <div class="controlled-open-dropdown-example__actions">
    <button kuiButton type="button" [kuiDropdownFor]="results">Show results</button>
    <button kuiButton type="button" shape="soft" (click)="resultsOpen.set(true)">
      Open from parent
    </button>
  </div>

  <kui-dropdown #results [(open)]="resultsOpen" panelWidth="anchor" [panelRole]="null">
    <div kuiOption value="alpha">Alpha result</div>
    <div kuiOption value="beta">Beta result</div>
  </kui-dropdown>
</div>

Inside kui-field

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>

Panel width

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 kuiButton type="button" [kuiDropdownFor]="anchorPanel">panelWidth="anchor"</button>
  <kui-dropdown #anchorPanel panelWidth="anchor">
    <div kuiOption value="a">Matches trigger width</div>
  </kui-dropdown>

  <button kuiButton 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 kuiButton type="button" [kuiDropdownFor]="explicitPanel">width="320px"</button>
  <kui-dropdown #explicitPanel width="320px">
    <div kuiOption value="c">Always exactly 320px wide</div>
  </kui-dropdown>
</div>

Provider defaults

Set defaults.dropdown once for the application or for a subtree. Each option resolves as local input > defaults.dropdown.<option> > built-in default.

import { provideKikitaUi } from '@kikita-labs/ui';

export const appConfig: ApplicationConfig = {
  providers: [
    provideKikitaUi({
      defaults: {
        dropdown: {
          /* options below */
        },
      },
    }),
  ],
};
NameTypeDefaultDescription
maxHeightstring | null — Maximum panel height as a CSS length, or null for no limit.
offsetnumber — Gap in px between the anchor and the panel edge.
closeOnSelectboolean — Closes the panel when a selectable option is clicked.
panelWidth'anchor' | 'content' | 'auto' — Panel width relative to the anchor.

Theming tokens

Set any of these on the component or an ancestor to restyle one part. Each token is optional: when it is not set, the part uses the role or scale token in the default column.

NameTypeDefaultDescription
--kui-dropdown-font-sizeCSS custom property--kui-text-sm-sizeFont size
--kui-dropdown-paddingCSS custom property--kui-space-1Padding
--kui-dropdown-max-heightCSS custom property240pxPanel max height (the maxHeight input and defaults.dropdown.maxHeight win)

API

API verified against @kikita-labs/ui v2.0.0 public typings.

NameTypeDefaultDescription
maxHeightstring | null | undefined'240px' (--kui-dropdown-max-height)Preferred max height of the panel before it scrolls. Falls back to defaults.dropdown.maxHeight, then the --kui-dropdown-max-height token (240px). 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.
offsetnumber | undefined4Gap in px between the anchor and the panel edge. A static numeric attribute is coerced. Falls back to defaults.dropdown.offset.
closeOnSelectboolean | undefinedtrueCloses the panel after an enabled option is selected with a pointer or Enter/Space. A plain input (it is no longer a two-way model). Falls back to defaults.dropdown.closeOnSelect.
openModelSignal<boolean>falseControlled open state. Bind with [(open)] to keep parent state synchronized with Escape, outside-click, and off-screen-anchor dismissal. Imperative open(), close(), and toggle() remain available.
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' | undefined'anchor'Falls back to defaults.dropdown.panelWidth. 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.
widthstring | nullnullExplicit panel width (any CSS width, e.g. 320px). Overrides panelWidth entirely when set.
isOpenSignal<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, focusReturn?)method-Sets the anchor imperatively. The optional third argument names the element that receives focus when the panel closes after a selection or Escape. 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]KuiDropdown-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] valueunknown-Required. The value an option renders and emits. Set via the `value` input on `KuiOption`, e.g. `<div kuiOption value="edit">`.
[kuiOption] disabledbooleanfalseDisables click and keyboard selection for this option.
kuiOptionSelectEventEmitter<unknown>-Emits the option value on selection.
--kui-dropdown-bgCSS custom propertyvar(--kui-color-surface-elevated)Panel background.
--kui-dropdown-borderCSS custom propertyvar(--kui-color-border)Panel border color.
--kui-dropdown-radiusCSS custom propertyvar(--kui-radius-md)Panel corner radius.
--kui-dropdown-shadowCSS custom propertyvar(--kui-shadow-lg)Panel drop shadow.
--kui-dropdown-viewport-marginCSS custom propertyvar(--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.
KuiDropdownOptionsinterface-Shape of defaults.dropdown: maxHeight, offset, closeOnSelect and panelWidth.

Accessibility

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, and aria-describedby through their own directive; Dropdown itself only renders the panel role set by panelRole (default listbox).
  • Options render role="option" inside the panel. Disabled options expose aria-disabled="true" and are skipped by selection.
  • Escape closes the panel.
  • Enter and Space select the focused option; by default (closeOnSelect true) selection also closes the panel.
  • Tab closes 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.md in the library repository for current review status.

Migrating to 2.0

Consumer-visible changes of @kikita-labs/ui 2.0 for Dropdown.

  • The classes are KuiDropdown, KuiDropdownFor and KuiOption (they had a Component or Directive suffix); ng update @kikita-labs/ui renames the imports.
  • maxHeight, offset, closeOnSelect and panelWidth are undefined when omitted and fall back to defaults.dropdown; closeOnSelect is a plain input, no longer a two-way model.
  • A panel closed after a selection returns focus to the control (Escape returns it to the field control), and setAnchor takes an optional third argument naming the focus-return element.
  • The default panel height is the --kui-dropdown-max-height token, and the viewport clamp no longer adds a second 32px margin.

On this page