Primitives
The Combobox primitive is a combination of a dropdown and an input field. It allows users to select from a list of options while filtering the list based on their input.
import { Component, computed, signal } from "@angular/core";
import { NgIcon, provideIcons } from "@ng-icons/core";
import { heroChevronDown } from "@ng-icons/heroicons/outline";
import {
NgpCombobox,
NgpComboboxButton,
NgpComboboxDropdown,
NgpComboboxInput,
NgpComboboxOption,
NgpComboboxPortal,
} from "ng-primitives/combobox";
@Component({
selector: "app-combobox",
imports: [
NgpCombobox,
NgpComboboxDropdown,
NgpComboboxOption,
NgpComboboxInput,
NgpComboboxPortal,
NgpComboboxButton,
NgIcon,
],
providers: [provideIcons({ heroChevronDown })],
template: `
<div
[(ngpComboboxValue)]="value"
(ngpComboboxValueChange)="filter.set($event)"
(ngpComboboxOpenChange)="resetOnClose($event)"
ngpCombobox
>
<input
[value]="filter()"
(input)="onFilterChange($event)"
placeholder="Select an option"
ngpComboboxInput
/>
<button ngpComboboxButton aria-label="Toggle dropdown">
<ng-icon name="heroChevronDown" />
</button>
<div *ngpComboboxPortal ngpComboboxDropdown>
@for (option of filteredOptions(); track option) {
<div [ngpComboboxOptionValue]="option" ngpComboboxOption>
{{ option }}
</div>
} @empty {
<div class="empty-message">No options found</div>
}
</div>
</div>
`,
styles: `
[ngpCombobox] {
display: flex;
justify-content: space-between;
align-items: center;
height: 36px;
width: 300px;
border-radius: 8px;
border: none;
background-color: var(--ngp-background);
box-shadow: var(--ngp-input-shadow);
box-sizing: border-box;
}
[ngpCombobox][data-focus] {
outline: 2px solid var(--ngp-focus-ring);
outline-offset: 2px;
}
[ngpComboboxInput] {
flex: 1;
padding: 0 16px;
border: none;
background-color: transparent;
color: var(--ngp-text);
font-family: inherit;
font-size: 14px;
padding: 0 16px;
outline: none;
height: 100%;
}
[ngpComboboxButton] {
display: inline-flex;
justify-content: center;
align-items: center;
height: 100%;
width: 36px;
background-color: transparent;
border: none;
color: var(--ngp-text);
cursor: pointer;
box-sizing: border-box;
}
[ngpComboboxDropdown] {
background-color: var(--ngp-background);
border: 1px solid var(--ngp-border);
padding: 0.25rem;
border-radius: 0.75rem;
outline: none;
position: absolute;
animation: popover-show 0.1s ease-out;
width: var(--ngp-combobox-width);
box-shadow: var(--ngp-shadow-lg);
box-sizing: border-box;
margin-top: 4px;
max-height: 240px;
overflow-y: auto;
z-index: 1001;
}
[ngpComboboxDropdown][data-enter] {
animation: combobox-show 0.1s ease-out;
}
[ngpComboboxDropdown][data-exit] {
animation: combobox-hide 0.1s ease-out;
}
[ngpComboboxOption] {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.375rem 0.75rem;
cursor: pointer;
border-radius: 0.5rem;
width: 100%;
height: 36px;
font-size: 14px;
color: var(--ngp-text-primary);
box-sizing: border-box;
}
[ngpComboboxOption][data-hover] {
background-color: var(--ngp-background-hover);
}
[ngpComboboxOption][data-press] {
background-color: var(--ngp-background-active);
}
[ngpComboboxOption][data-active] {
background-color: var(--ngp-background-active);
}
.empty-message {
display: flex;
justify-content: center;
align-items: center;
padding: 0.5rem;
color: var(--ngp-text-secondary);
font-size: 14px;
font-weight: 500;
text-align: center;
}
@keyframes combobox-show {
0% {
opacity: 0;
transform: translateY(-10px) scale(0.9);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes combobox-hide {
0% {
opacity: 1;
transform: translateY(0) scale(1);
}
100% {
opacity: 0;
transform: translateY(-10px) scale(0.9);
}
}
`,
})
export default class ComboboxExample {
/** The options for the combobox. */
readonly options: string[] = [
"Marty McFly",
"Doc Brown",
"Biff Tannen",
"George McFly",
"Jennifer Parker",
"Emmett Brown",
"Einstein",
"Clara Clayton",
"Needles",
"Goldie Wilson",
"Marvin Berry",
"Lorraine Baines",
"Strickland",
];
/** The selected value. */
readonly value = signal<string | undefined>(undefined);
/** The filter value. */
readonly filter = signal<string>("");
/** Get the filtered options. */
protected readonly filteredOptions = computed(() =>
this.options.filter((option) =>
option.toLowerCase().includes(this.filter().toLowerCase()),
),
);
protected onFilterChange(event: Event): void {
const input = event.target as HTMLInputElement;
this.filter.set(input.value);
}
protected resetOnClose(open: boolean): void {
// if the dropdown is closed, reset the filter value
if (open) {
return;
}
// if the filter value is empty, set the value to undefined
if (this.filter() === "") {
this.value.set(undefined);
} else {
// otherwise set the filter value to the selected value
this.filter.set(this.value() ?? "");
}
}
}
Import the Combobox primitives from ng-primitives/combobox
.
import {
NgpCombobox,
NgpComboboxButton,
NgpComboboxDropdown,
NgpComboboxInput,
NgpComboboxOption,
NgpComboboxPortal,
} from 'ng-primitives/combobox';
Assemble the combobox directives in your template.
<div ngpCombobox>
<input ngpComboboxInput />
<button ngpComboboxButton>▼</button>
<div ngpComboboxDropdown>
@for (option of options; track option) {
<div ngpComboboxOption [ngpComboboxOptionValue]="option">{{ option }}</div>
}
</div>
</div>
Create a reusable component that uses the NgpCombobox
directive.
import { Component } from '@angular/core';
import { Combobox } from './combobox';
@Component({
selector: 'app-root',
imports: [Combobox],
template: `
<app-combobox [options]="options" placeholder="Select a character" />
`,
})
export default class App {
readonly options: string[] = [
'Marty McFly',
'Doc Brown',
'Biff Tannen',
'George McFly',
'Jennifer Parker',
'Emmett Brown',
'Einstein',
'Clara Clayton',
'Needles',
'Goldie Wilson',
'Marvin Berry',
'Lorraine Baines',
'Strickland',
];
}
Generate a reusable combobox component using the Angular CLI.
ng g ng-primitives:primitive combobox
path
: The path at which to create the component file.prefix
: The prefix to apply to the generated component selector.componentSuffix
: The suffix to apply to the generated component class name.fileSuffix
: The suffix to apply to the generated component file name. Defaults to component
.exampleStyles
: Whether to include example styles in the generated component file. Defaults to true
.The following directives are available to import from the ng-primitives/combobox
package:
The main container for the combobox.
The value of the combobox.
Whether the combobox is multiple selection.
Whether the combobox is disabled.
The comparator function used to compare options.
The position of the dropdown.
Event emitted when the value changes.
Emit when the dropdown open state changes.
[ngpCombobox]
ngpCombobox
The following data attributes are applied to the ngpCombobox
directive:
Attribute | Description |
---|---|
data-open |
Applied when the combobox is open. |
data-disabled |
Applied when the combobox is disabled. |
data-multiple |
Applied when the combobox is in multiple mode. |
data-hover |
Applied when the combobox is hovered. |
data-press |
Applied when the combobox is pressed. |
data-focus |
Applied when the combobox has focus within it. |
data-invalid |
Applied when the combobox is invalid. |
data-valid |
Applied when the combobox is valid. |
data-touched |
Applied when the combobox has been touched. |
data-pristine |
Applied when the combobox is pristine (not modified). |
data-dirty |
Applied when the combobox has been modified. |
data-pending |
Applied when the combobox is pending (e.g., async validation). |
data-disabled |
Applied when the combobox is disabled. |
The button that toggles the combobox dropdown.
[ngpComboboxButton]
ngpComboboxButton
The following data attributes are applied to the ngpComboboxButton
directive:
Attribute | Description |
---|---|
data-open |
Applied when the combobox is open. |
data-disabled |
Applied when the combobox is disabled. |
data-multiple |
Applied when the combobox is in multiple mode. |
The dropdown that contains the combobox options.
[ngpComboboxDropdown]
ngpComboboxDropdown
The following CSS custom properties are applied to the ngpComboboxDropdown
directive:
Property | Description |
---|---|
--ngp-combobox-transform-origin |
The transform origin for the dropdown animation. |
--ngp-combobox-width |
The width of the combobox dropdown. |
--ngp-combobox-input-width |
The width of the combobox input field. |
--ngp-combobox-button-width |
The width of the combobox button. |
The input field for the combobox.
input[ngpComboboxInput]
ngpComboboxInput
The following data attributes are applied to the ngpComboboxInput
directive:
Attribute | Description |
---|---|
data-open |
Applied when the combobox is open. |
data-disabled |
Applied when the combobox is disabled. |
data-multiple |
Applied when the combobox is in multiple mode. |
data-invalid |
Applied when the input is invalid. |
data-valid |
Applied when the input is valid. |
data-touched |
Applied when the input has been touched. |
data-pristine |
Applied when the input is pristine (not modified). |
data-dirty |
Applied when the input has been modified. |
data-pending |
Applied when the input is pending (e.g., async validation). |
data-disabled |
Applied when the input is disabled. |
The individual options within the combobox dropdown.
The disabled state of the option.
[ngpComboboxOption]
ngpComboboxOption
The following data attributes are applied to the ngpComboboxOption
directive:
Attribute | Description |
---|---|
data-selected |
Applied when the option is selected. |
data-active |
Applied when the option is active. |
data-disabled |
Applied when the option is disabled. |
The portal for rendering the combobox dropdown in an overlay.
[ngpComboboxPortal]
ngpComboboxPortal
The ngpComboboxDropdown
primitive adds a CSS custom property --ngp-combobox-transform-origin
to the element that can be used to animate the menu from the trigger element.
The ngpComboboxDropdown
will also add the data-enter
and data-exit
attributes to the element when it is being added or removed from the DOM. This can be used to trigger animations.
:host[data-enter] {
animation: fade-in 0.2s ease-in-out;
}
:host[data-exit] {
animation: fade-out 0.2s ease-in-out;
}
Adheres to the WAI-ARIA guidelines for comboboxes.
Copyright © 2025 Angular Primitives