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;
}
[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 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. |
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. |
The individual options within the combobox dropdown.
The value of the option.
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