Combobox
Searchable native input trigger with projected options, local filtering, and async mode.
Combobox composes field, dropdown, option, and the highlight pipe with the combobox directive.
import {
KuiComboboxDirective,
KuiComboboxHighlightPipe,
KuiDropdownComponent,
KuiFieldComponent,
KuiOptionDirective,
kuiProvideComboboxOptions,
} from '@kikita-labs/ui';Bind value and query with Angular signals. Filter and render projected options yourself.
<kui-field label="Assignee" hint="Type to filter projected options">
<input
kuiCombobox
[(value)]="assignee"
[(query)]="query"
[kuiLabelFn]="personLabel"
placeholder="Search people..."
(search)="query.set($event)"
/>
<kui-dropdown>
@for (person of filteredPeople(); track person.id) {
<button kuiOption [value]="person">
<span class="kui-combobox-match-label">
@for (segment of person.name | kuiComboboxHighlight: query(); track $index) {
@if (segment.match) {
<mark class="kui-combobox-highlight">{{ segment.text }}</mark>
} @else {
<span>{{ segment.text }}</span>
}
}
</span>
</button>
} @empty {
<div class="kui-combobox-empty">No people found</div>
}
</kui-dropdown>
</kui-field>Use (search) for remote requests. The directive does not own async data; update the projected options when results arrive. Use mode="async" to document that filtering happens outside the directive.
<kui-field label="Reviewer" hint="Simulates a remote lookup with a loading row">
<input
kuiCombobox
mode="async"
[(value)]="reviewer"
[(query)]="reviewerQuery"
[loading]="loading()"
[kuiLabelFn]="personLabel"
placeholder="Type to search..."
(search)="loadReviewers($event)"
/>
<kui-dropdown>
@if (loading()) {
<div class="kui-combobox-loading-row">
<span class="kui-combobox-loader" aria-hidden="true"></span>
Loading people
</div>
} @else if (errorMessage(); as lookupError) {
<div class="kui-combobox-empty" role="alert">{{ lookupError }}</div>
} @else {
@for (person of reviewers(); track person.id) {
<button kuiOption [value]="person">{{ person.name }}</button>
} @empty {
<div class="kui-combobox-empty">No matches</div>
}
}
</kui-dropdown>
</kui-field>mode="free" stores typed text as the value. In the default filter mode, editing text clears the selected value until a projected option is chosen.
<kui-field label="Tag" hint="Type a custom tag or choose a suggestion">
<input kuiCombobox mode="free" [(value)]="tag" placeholder="Type or choose..." />
<kui-dropdown>
<button kuiOption value="Bug">Bug</button>
<button kuiOption value="Feature">Feature</button>
<button kuiOption value="Regression">Regression</button>
</kui-dropdown>
</kui-field>Combobox inherits field id, label association, aria-describedby, invalid state, and field size from kui-field.
<div class="combobox-field-states-example">
<kui-field label="Owner" hint="Disabled prevents editing and opening the dropdown">
<input kuiCombobox disabled [(value)]="disabledOwner" placeholder="Search people..." />
<kui-dropdown>
<button kuiOption value="engineer">Software Engineer</button>
<button kuiOption value="designer">Designer</button>
</kui-dropdown>
</kui-field>
<kui-field label="Owner" error="Choose an owner before continuing">
<input kuiCombobox invalid [(value)]="invalidOwner" placeholder="Search people..." />
<kui-dropdown>
<button kuiOption value="engineer">Software Engineer</button>
<button kuiOption value="designer">Designer</button>
</kui-dropdown>
</kui-field>
</div>Use provider defaults for app-wide combobox behavior. Local inputs still win.
import { kuiProvideComboboxOptions } from '@kikita-labs/ui';
export const appConfig: ApplicationConfig = {
providers: [
kuiProvideComboboxOptions({
clearable: true,
}),
],
};Inputs and outputs verified against @kikita-labs/ui v1.6.1 public typings.
| Name | Type | Default | Description |
|---|---|---|---|
| value | T | string | null | null | Selected value. Bound by [formField] or [(value)]. |
| query | string | '' | Current search text shown in the native input. |
| search | output: string | - | Emitted on every native input edit. Use for local filtering or remote requests. |
| kuiLabelFn | (item: T) => string | String() | Maps a selected object value to display text. Required when T is not a primitive. |
| placeholder | string | '' | Native input placeholder shown when no value is selected. |
| mode | 'filter' | 'free' | 'async' | 'filter' | filter clears the value while editing until a kuiOption is selected. free stores typed text as the value. async documents that filtering happens outside the directive. |
| clearable | boolean | undefined | true | Shows a clear affordance. Falls back to KUI_COMBOBOX_OPTIONS, then KUI_FIELD_OPTIONS, then true. |
| loading | boolean | false | Shows a suffix loader. Loading row content inside kui-dropdown is projected by the consumer. |
| disabled | boolean | false | Disables the native input. Set by [formField] or directly. |
| readonly | boolean | false | Keeps the value readable but prevents editing and opening the dropdown. |
| invalid | boolean | false | Applies ARIA invalid state. kui-field also contributes invalid state from validation. |
| errors | readonly ValidationError[] | [] | Validation errors set by [formField], consumed by kui-field for automatic error text. |
| touched | boolean | false | Touched state set by [formField] or Signal Forms. |
| touch | output: void | - | Emitted when an opened dropdown closes; marks the control touched for Signal Forms. |
| id | string | undefined | undefined | Explicit id override. Inside kui-field, the field id is used when omitted. |
| kuiOption | directive | - | Marks a projected option inside kui-dropdown. Provides role="option", aria-selected, disabled state, and keyboard navigation. |
| kuiComboboxHighlight | pipe: (label: string, query: string | null | undefined) => readonly { text: string; match: boolean }[] | - | Splits an option label into plain and matched segments for highlighting the current query. |
| kuiProvideComboboxOptions | (opts: KuiComboboxOptions) => Provider | - | Registers app-wide combobox defaults, such as clearable, via KUI_COMBOBOX_OPTIONS. |
| KUI_COMBOBOX_OPTIONS | InjectionToken<KuiComboboxOptions> | - | Injection token backing kuiProvideComboboxOptions. Read by the directive for clearable fallback. |
| --kui-combobox-affordance-size | CSS custom property | - | Size of the suffix clear/chevron affordance controls. |
| --kui-combobox-suffix-gap | CSS custom property | - | Gap between suffix affordances (clear button, chevron, loader). |
| --kui-combobox-loader-size | CSS custom property | - | Diameter of the suffix loading spinner. |
| --kui-combobox-loader-border-width | CSS custom property | - | Border width of the suffix loading spinner. |
| --kui-combobox-loader-duration | CSS custom property | - | Animation duration of the suffix loading spinner. |
| --kui-combobox-highlight-bg | CSS custom property | - | Background color of matched text inside kui-combobox-highlight. |
| --kui-combobox-highlight-text | CSS custom property | - | Text color of matched text inside kui-combobox-highlight. |
| --kui-combobox-highlight-radius | CSS custom property | - | Corner radius of the matched-text highlight mark. |
Combobox keeps native input semantics and composite listbox behavior on the dropdown.
- The host remains a native
<input>. - The input uses
role="combobox",aria-haspopup="listbox",aria-expanded, andaria-controls. kui-fieldprovides label, hint, error, required marker,aria-describedby, and inherited invalid state.kui-dropdownrenders the popup listbox through Angular CDK overlay.kuiOptionprovidesrole="option",aria-selected, disabled state, click selection, and keyboard navigation.- Arrow keys open the dropdown and move focus to the first or last enabled option.
- Escape closes the dropdown.
- Multiple chip-backed selection is intentionally not part of Combobox; use Select for fixed choices without typing.