Select
A custom dropdown for picking one value, built for Angular forms.
import { โฆ } from '@inlaylab/ivorie/select'Value:
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IV_SELECT_IMPORTS } from '@inlaylab/ivorie/select';
@Component({
selector: 'demo-select',
imports: [IV_SELECT_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-col items-center gap-3">
<iv-select [(value)]="fruit" placeholder="Pick a fruit">
<iv-select-trigger class="w-48" aria-label="Fruit" />
<iv-select-content>
<iv-select-item value="apple">Apple</iv-select-item>
<iv-select-item value="banana">Banana</iv-select-item>
<iv-select-item value="orange">Orange</iv-select-item>
</iv-select-content>
</iv-select>
<p class="text-muted-foreground text-sm">Value: {{ fruit() }}</p>
</div>
`,
})
export default class SelectDemo {
protected readonly fruit = signal<string | null>(null);
}
Installation
npx ivorie add selectimport { โฆ } from '@inlaylab/ivorie/select'A styled dropdown, not a native <select>. It supports ngModel, reactive forms, groups, labels and separators.
Usage
import { IV_SELECT_IMPORTS } from '@inlaylab/ivorie/select';<iv-select [(value)]="fruit" placeholder="Pick a fruit">
<iv-select-trigger />
<iv-select-content>
<iv-select-item value="apple">Apple</iv-select-item>
<iv-select-item value="banana">Banana</iv-select-item>
</iv-select-content>
</iv-select>Examples
With ngModel
Value: banana
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { IV_SELECT_IMPORTS } from '@inlaylab/ivorie/select';
@Component({
selector: 'demo-select-ngmodel',
imports: [FormsModule, IV_SELECT_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-col items-center gap-3">
<iv-select [(ngModel)]="fruit" placeholder="Pick a fruit">
<iv-select-trigger class="w-48" aria-label="Fruit" />
<iv-select-content>
<iv-select-item value="apple">Apple</iv-select-item>
<iv-select-item value="banana">Banana</iv-select-item>
<iv-select-item value="orange">Orange</iv-select-item>
</iv-select-content>
</iv-select>
<p class="text-muted-foreground text-sm">Value: {{ fruit() }}</p>
</div>
`,
})
export default class SelectNgmodelDemo {
protected readonly fruit = signal<string | null>('banana');
}
Reactive forms
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { IV_SELECT_IMPORTS } from '@inlaylab/ivorie/select';
@Component({
selector: 'demo-select-reactive-forms',
imports: [ReactiveFormsModule, IV_SELECT_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<form [formGroup]="form" class="flex flex-col items-center gap-3">
<iv-select formControlName="fruit" placeholder="Pick a fruit">
<iv-select-trigger class="w-48" aria-label="Fruit" />
<iv-select-content>
<iv-select-item value="apple">Apple</iv-select-item>
<iv-select-item value="banana">Banana</iv-select-item>
<iv-select-item value="orange">Orange</iv-select-item>
</iv-select-content>
</iv-select>
<p class="text-muted-foreground text-sm">Value: {{ form.value.fruit }}</p>
</form>
`,
})
export default class SelectReactiveFormsDemo {
protected readonly form = new FormGroup({ fruit: new FormControl<string | null>('apple') });
}
Groups
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IV_SELECT_IMPORTS } from '@inlaylab/ivorie/select';
@Component({
selector: 'demo-select-groups',
imports: [IV_SELECT_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<iv-select [(value)]="city" placeholder="Pick a city">
<iv-select-trigger class="w-56" aria-label="City" />
<iv-select-content>
<iv-select-group>
<iv-select-label>North America</iv-select-label>
<iv-select-item value="nyc">New York</iv-select-item>
<iv-select-item value="la">Los Angeles</iv-select-item>
</iv-select-group>
<iv-select-separator />
<iv-select-group>
<iv-select-label>Europe</iv-select-label>
<iv-select-item value="lon">London</iv-select-item>
<iv-select-item value="par">Paris</iv-select-item>
</iv-select-group>
</iv-select-content>
</iv-select>
`,
})
export default class SelectGroupsDemo {
protected readonly city = signal<string | null>(null);
}
Small
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IV_SELECT_IMPORTS } from '@inlaylab/ivorie/select';
@Component({
selector: 'demo-select-small',
imports: [IV_SELECT_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<iv-select [(value)]="fruit" size="sm" placeholder="Pick a fruit">
<iv-select-trigger class="w-48" aria-label="Fruit" />
<iv-select-content>
<iv-select-item value="apple">Apple</iv-select-item>
<iv-select-item value="banana">Banana</iv-select-item>
<iv-select-item value="orange">Orange</iv-select-item>
</iv-select-content>
</iv-select>
`,
})
export default class SelectSmallDemo {
protected readonly fruit = signal<string | null>(null);
}
Disabled
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IV_SELECT_IMPORTS } from '@inlaylab/ivorie/select';
@Component({
selector: 'demo-select-disabled',
imports: [IV_SELECT_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<iv-select value="apple" [disabled]="true">
<iv-select-trigger class="w-48" aria-label="Fruit" />
<iv-select-content>
<iv-select-item value="apple">Apple</iv-select-item>
<iv-select-item value="banana">Banana</iv-select-item>
</iv-select-content>
</iv-select>
`,
})
export default class SelectDisabledDemo {}
Disabled item
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IV_SELECT_IMPORTS } from '@inlaylab/ivorie/select';
@Component({
selector: 'demo-select-disabled-item',
imports: [IV_SELECT_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<iv-select [(value)]="plan" placeholder="Pick a plan">
<iv-select-trigger class="w-52" aria-label="Plan" />
<iv-select-content>
<iv-select-item value="free">Free</iv-select-item>
<iv-select-item value="pro">Pro</iv-select-item>
<iv-select-item value="enterprise" [disabled]="true">Enterprise (unavailable)</iv-select-item>
</iv-select-content>
</iv-select>
`,
})
export default class SelectDisabledItemDemo {
protected readonly plan = signal<string | null>(null);
}
Filter label
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IV_SELECT_IMPORTS } from '@inlaylab/ivorie/select';
@Component({
selector: 'demo-select-filter-label',
imports: [IV_SELECT_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<iv-select [(value)]="status" placeholder="All">
<iv-select-trigger label="Status" resetValue="all" class="w-52" aria-label="Status filter" />
<iv-select-content>
<iv-select-item value="all">All</iv-select-item>
<iv-select-item value="active">Active</iv-select-item>
<iv-select-item value="archived">Archived</iv-select-item>
</iv-select-content>
</iv-select>
`,
})
export default class SelectFilterLabelDemo {
protected readonly status = signal<string | null>('active');
}
API
Every part also accepts class.
iv-select
| Name | Type | Default | Description |
|---|---|---|---|
value |
string | null |
null |
Selected value. Bind with [(value)]. |
placeholder |
string |
'Select an option' |
Shown when no item matches the value. |
disabled |
boolean |
false |
Disables the trigger. Also disabled when the form control is disabled. |
size |
'default' | 'sm' |
'default' |
Trigger size. |
Outputs:
| Name | Payload | Description |
|---|---|---|
valueChange |
string | null |
Emits when the user selects an item. |
iv-select-trigger
Renders a native button. class goes on that button. The trigger is w-fit; add class="w-full" to fill its container. A truncated value shows its full text in a tooltip on hover.
| Name | Type | Default | Description |
|---|---|---|---|
id |
string | null |
null |
Set on the inner button, so <label for> works. |
aria-label |
string | null |
null |
Set on the inner button. |
aria-labelledby |
string | null |
null |
Set on the inner button. |
size |
'default' | 'sm' | undefined |
undefined |
Overrides the root size. |
label |
string |
'' |
Static prefix before the value, such as Feature: All. For filter bars. |
resetValue |
string |
'' |
Value that means "no filter". With label set, any other value marks the trigger active (data-active). |
iv-select-content
The dropdown panel, shown in a CDK overlay. No inputs besides class.
iv-select-group
Wraps related items. Has role="group". No inputs besides class.
iv-select-label
Muted heading for a group. No inputs besides class.
iv-select-item
| Name | Type | Default | Description |
|---|---|---|---|
value |
string |
required | Value set on selection. |
disabled |
boolean |
false |
Blocks selection of this item. |
iv-select-separator
A divider line. Hidden from assistive tech. No inputs besides class.
Accessibility
- Focus stays on the trigger. It has
role="combobox",aria-expanded,aria-controlsandaria-activedescendant. - The list has
role="listbox". Items haverole="option",aria-selectedandaria-disabled. Enter,Space,ArrowDownandArrowUpopen the list on the selected item, else the first.- While open: arrows,
Home,Endand typeahead move the active option and skip disabled items. EnterorSpaceselects and closes.Escapecloses and returns focus to the trigger.Tabcloses.Escapeis handled, so an enclosing dialog stays open.
Notes
- The trigger shows the text of the item whose
valuematches. Items added later (for example from async data) update it. No match shows the placeholder. - The list is a CDK overlay, so parent
overflow: hiddendoes not clip it. A click outside closes it. - Closing marks the form control touched.
ngModelandformControlNameneedFormsModuleorReactiveFormsModule.