Select
A select displays a collapsible list of options and allows a user to select one of them.
Import
import { AvSelectImports } from '@avesra/angular';Usage
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
AvLabelComponent,
AvSelectImports,
} from '@avesra/angular';
@Component({
selector: 'app-select-basic-demo',
imports: [
FormsModule,
AvSelectImports,
AvLabelComponent,
],
template: `<div av-select class="w-64" placeholder="Select one" [(ngModel)]="value">
<label av-label>State</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
<div av-list-box-item id="florida" textValue="Florida">
Florida
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="delaware" textValue="Delaware">
Delaware
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="california" textValue="California">
California
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="texas" textValue="Texas">
Texas
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="new-york" textValue="New York">
New York
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="washington" textValue="Washington">
Washington
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
</div>`,
})
export class SelectBasicDemo {
value: string | null = null;
}Anatomy
Import the Select parts and compose them with attribute selectors. Options live in an independent List Box inside av-select-popover — List Box is not coupled to Select beyond composition.
<div av-select>
<label av-label></label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<p av-description></p>
<av-select-popover>
<div av-list-box>
<div av-list-box-item>
<label av-label></label>
<p av-description></p>
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-section>
<p></p>
<div av-list-box-item>
<label av-label></label>
</div>
</div>
</div>
</av-select-popover>
</div>Variants
The Select component supports two visual variants:
primary(default) — Standard styling with shadow, suitable for most use casessecondary— Lower emphasis variant without shadow, suitable for use in Surface components
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
AvLabelComponent,
AvSelectImports,
} from '@avesra/angular';
@Component({
selector: 'app-select-variants-demo',
imports: [
FormsModule,
AvSelectImports,
AvLabelComponent,
],
template: `<div class="flex flex-col gap-4">
<div av-select class="w-64" placeholder="Select one" variant="primary" [(ngModel)]="primaryValue">
<label av-label>Primary variant</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box aria-label="Options" selection-mode="single">
<div av-list-box-item id="option1" textValue="Option 1">
Option 1
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="option2" textValue="Option 2">
Option 2
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
</div>
<div av-select class="w-64" placeholder="Select one" variant="secondary" [(ngModel)]="secondaryValue">
<label av-label>Secondary variant</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box aria-label="Options" selection-mode="single">
<div av-list-box-item id="option1" textValue="Option 1">
Option 1
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="option2" textValue="Option 2">
Option 2
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
</div>
</div>`,
})
export class SelectVariantsDemo {
primaryValue: string | null = null;
secondaryValue: string | null = null;
}Full Width
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
AvLabelComponent,
AvSelectImports,
} from '@avesra/angular';
@Component({
selector: 'app-select-full-width-demo',
imports: [
FormsModule,
AvSelectImports,
AvLabelComponent,
],
template: `<div class="w-[400px] space-y-4">
<div av-select full-width placeholder="Select one" [(ngModel)]="value">
<label av-label>Favorite Animal</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
<div av-list-box-item id="cat" textValue="Cat">
Cat
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="dog" textValue="Dog">
Dog
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="bird" textValue="Bird">
Bird
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
</div>
</div>`,
})
export class SelectFullWidthDemo {
value: string | null = null;
}With Description
Select your state of residence
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
AvDescriptionComponent,
AvLabelComponent,
AvSelectImports,
} from '@avesra/angular';
@Component({
selector: 'app-select-with-description-demo',
imports: [
FormsModule,
AvSelectImports,
AvLabelComponent,
AvDescriptionComponent,
],
template: `<div av-select class="w-64" placeholder="Select one" [(ngModel)]="value">
<label av-label>State</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
<div av-list-box-item id="florida" textValue="Florida">
Florida
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="delaware" textValue="Delaware">
Delaware
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="california" textValue="California">
California
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="texas" textValue="Texas">
Texas
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="new-york" textValue="New York">
New York
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="washington" textValue="Washington">
Washington
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
<p av-description>Select your state of residence</p>
</div>`,
})
export class SelectWithDescriptionDemo {
value: string | null = null;
}Required
Use ReactiveFormsModule with Validators.required , bind [invalid] from the control state, and show av-field-error when validation fails.
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import {
AvButtonComponent,
AvFieldErrorComponent,
AvFormComponent,
AvLabelComponent,
AvSelectImports,
} from '@avesra/angular';
@Component({
selector: 'app-select-required-demo',
imports: [
ReactiveFormsModule,
AvFormComponent,
AvSelectImports,
AvLabelComponent,
AvFieldErrorComponent,
AvButtonComponent,
],
template: `<form
av-form
novalidate
class="flex w-[256px] flex-col gap-4"
[formGroup]="form"
(ngSubmit)="onSubmit()"
>
<div
av-select
full-width
placeholder="Select one"
formControlName="state"
[invalid]="isStateInvalid()"
>
<label av-label required [invalid]="isStateInvalid()">State</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
<div av-list-box-item id="florida" textValue="Florida">
Florida
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="delaware" textValue="Delaware">
Delaware
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="california" textValue="California">
California
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="texas" textValue="Texas">
Texas
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="new-york" textValue="New York">
New York
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="washington" textValue="Washington">
Washington
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
<p av-field-error [visible]="isStateInvalid()">Please select an option.</p>
</div>
<div
av-select
full-width
placeholder="Select a country"
formControlName="country"
[invalid]="isCountryInvalid()"
>
<label av-label required [invalid]="isCountryInvalid()">Country</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
<div av-list-box-item id="usa" textValue="United States">
United States
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="canada" textValue="Canada">
Canada
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="mexico" textValue="Mexico">
Mexico
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="uk" textValue="United Kingdom">
United Kingdom
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="france" textValue="France">
France
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="germany" textValue="Germany">
Germany
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
<p av-field-error [visible]="isCountryInvalid()">Please select an option.</p>
</div>
<button av-button type="submit">Submit</button>
</form>`,
})
export class SelectRequiredDemo {
readonly form = new FormGroup({
state: new FormControl<string | null>(null, Validators.required),
country: new FormControl<string | null>(null, Validators.required),
});
isStateInvalid(): boolean {
const control = this.form.controls.state;
return control.invalid && (control.touched || control.dirty);
}
isCountryInvalid(): boolean {
const control = this.form.controls.country;
return control.invalid && (control.touched || control.dirty);
}
onSubmit(): void {
this.form.markAllAsTouched();
if (this.form.invalid) {
return;
}
alert('Form submitted successfully!');
}
}Disabled
import { Component } from '@angular/core';
import {
AvLabelComponent,
AvSelectImports,
} from '@avesra/angular';
@Component({
selector: 'app-select-disabled-demo',
imports: [
AvSelectImports,
AvLabelComponent,
],
template: `<div class="flex flex-col gap-4">
<div
av-select
class="w-64"
disabled
placeholder="Select one"
[default-selected-keys]="['california']"
>
<label av-label>State</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
<div av-list-box-item id="florida" textValue="Florida">
Florida
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="delaware" textValue="Delaware">
Delaware
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="california" textValue="California">
California
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="texas" textValue="Texas">
Texas
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="new-york" textValue="New York">
New York
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="washington" textValue="Washington">
Washington
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
</div>
<div
av-select
class="w-64"
disabled
selection-mode="multiple"
placeholder="Select countries"
[default-selected-keys]="['argentina', 'japan', 'france']"
>
<label av-label>Countries to Visit</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box selection-mode="multiple">
<div av-list-box-item id="argentina" textValue="Argentina">
Argentina
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="venezuela" textValue="Venezuela">
Venezuela
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="japan" textValue="Japan">
Japan
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="france" textValue="France">
France
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="italy" textValue="Italy">
Italy
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="spain" textValue="Spain">
Spain
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
</div>
</div>`,
})
export class SelectDisabledDemo {}With Disabled Options
Set disabled on an individual av-list-box-item to prevent selection. The item stays visible but is skipped by keyboard navigation.
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
AvLabelComponent,
AvSelectImports,
} from '@avesra/angular';
@Component({
selector: 'app-select-with-disabled-options-demo',
imports: [
FormsModule,
AvSelectImports,
AvLabelComponent,
],
template: `<div av-select class="w-64" placeholder="Select an animal" [(ngModel)]="value">
<label av-label>Animal</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
<div av-list-box-item id="dog" textValue="Dog">
Dog
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="cat" textValue="Cat" disabled>
Cat
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="bird" textValue="Bird">
Bird
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="kangaroo" textValue="Kangaroo" disabled>
Kangaroo
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="elephant" textValue="Elephant">
Elephant
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="tiger" textValue="Tiger">
Tiger
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
</div>`,
})
export class SelectWithDisabledOptionsDemo {
value: string | null = null;
}Multiple Select
Set selection-mode="multiple" on both av-select and the nested av-list-box so users can pick several options without closing the popover.
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
AvLabelComponent,
AvSelectImports,
} from '@avesra/angular';
@Component({
selector: 'app-select-multiple-demo',
imports: [
FormsModule,
AvSelectImports,
AvLabelComponent,
],
template: `<div
av-select
class="w-64"
placeholder="Select countries"
selection-mode="multiple"
[(ngModel)]="value"
>
<label av-label>Countries to Visit</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box selection-mode="multiple">
<div av-list-box-item id="argentina" textValue="Argentina">
Argentina
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="venezuela" textValue="Venezuela">
Venezuela
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="japan" textValue="Japan">
Japan
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="france" textValue="France">
France
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="italy" textValue="Italy">
Italy
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="spain" textValue="Spain">
Spain
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="thailand" textValue="Thailand">
Thailand
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="new-zealand" textValue="New Zealand">
New Zealand
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="iceland" textValue="Iceland">
Iceland
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
</div>`,
})
export class SelectMultipleDemo {
value: string[] = [];
}With Sections
Group options with av-list-box-section and separate groups with av-separator .
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
AvLabelComponent,
AvSelectImports,
AvSeparatorImports,
} from '@avesra/angular';
@Component({
selector: 'app-select-with-sections-demo',
imports: [
FormsModule,
AvSelectImports,
AvSeparatorImports,
AvLabelComponent,
],
template: `<div av-select class="w-64" placeholder="Select a country" [(ngModel)]="value">
<label av-label>Country</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
<div av-list-box-section>
<p class="px-2.5 py-1 text-xs font-medium text-muted">North America</p>
<div av-list-box-item id="usa" textValue="United States">
United States
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="canada" textValue="Canada">
Canada
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="mexico" textValue="Mexico">
Mexico
<span av-list-box-item-indicator></span>
</div>
</div>
<div av-separator></div>
<div av-list-box-section>
<p class="px-2.5 py-1 text-xs font-medium text-muted">Europe</p>
<div av-list-box-item id="uk" textValue="United Kingdom">
United Kingdom
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="france" textValue="France">
France
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="germany" textValue="Germany">
Germany
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="spain" textValue="Spain">
Spain
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="italy" textValue="Italy">
Italy
<span av-list-box-item-indicator></span>
</div>
</div>
<div av-separator></div>
<div av-list-box-section>
<p class="px-2.5 py-1 text-xs font-medium text-muted">Asia</p>
<div av-list-box-item id="japan" textValue="Japan">
Japan
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="china" textValue="China">
China
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="india" textValue="India">
India
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="south-korea" textValue="South Korea">
South Korea
<span av-list-box-item-indicator></span>
</div>
</div>
</div>
</av-select-popover>
</div>`,
})
export class SelectWithSectionsDemo {
value: string | null = null;
}Controlled
Drive selection from outside with [(selectedKeys)] on av-select . Prefer this over ngModel when the parent owns the selected keys.
Selected: California
import { Component, computed, signal } from '@angular/core';
import {
AvLabelComponent,
AvSelectImports,
} from '@avesra/angular';
@Component({
selector: 'app-select-controlled-demo',
imports: [
AvSelectImports,
AvLabelComponent,
],
template: `<div class="space-y-2">
<div
av-select
class="w-64"
placeholder="Select a state"
[(selectedKeys)]="selected"
>
<label av-label>State (controlled)</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
@for (state of states; track state.id) {
<div av-list-box-item [id]="state.id" [textValue]="state.name">
{{ state.name }}
<span av-list-box-item-indicator></span>
</div>
}
</div>
</av-select-popover>
</div>
<p class="text-sm text-muted">Selected: {{ selectedLabel() }}</p>
</div>`,
})
export class SelectControlledDemo {
readonly states = [
{
"id": "california",
"name": "California"
},
{
"id": "texas",
"name": "Texas"
},
{
"id": "florida",
"name": "Florida"
},
{
"id": "new-york",
"name": "New York"
},
{
"id": "illinois",
"name": "Illinois"
},
{
"id": "pennsylvania",
"name": "Pennsylvania"
}
];
readonly selected = signal<string[]>(['california']);
readonly selectedLabel = computed(() => {
const key = this.selected()[0];
return this.states.find((state) => state.id === key)?.name ?? 'None';
});
}Controlled Multiple
Selected: california, texas
import { Component, computed, signal } from '@angular/core';
import {
AvLabelComponent,
AvSelectImports,
} from '@avesra/angular';
@Component({
selector: 'app-select-controlled-multiple-demo',
imports: [
AvSelectImports,
AvLabelComponent,
],
template: `<div class="space-y-4">
<div
av-select
class="w-64"
placeholder="Select states"
selection-mode="multiple"
[(selectedKeys)]="selected"
>
<label av-label>States (controlled multiple)</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box selection-mode="multiple">
<div av-list-box-item id="california" textValue="California">
California
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="texas" textValue="Texas">
Texas
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="florida" textValue="Florida">
Florida
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="new-york" textValue="New York">
New York
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="illinois" textValue="Illinois">
Illinois
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="pennsylvania" textValue="Pennsylvania">
Pennsylvania
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
</div>
<p class="text-sm text-muted">Selected: {{ selectedLabel() }}</p>
</div>`,
})
export class SelectControlledMultipleDemo {
readonly selected = signal<string[]>(['california', 'texas']);
readonly selectedLabel = computed(() => {
const keys = this.selected();
return keys.length > 0 ? keys.join(', ') : 'None';
});
}Controlled Open State
Control the popover with [(open)] two-way binding.
Select is closed
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
AvButtonComponent,
AvLabelComponent,
AvSelectImports,
} from '@avesra/angular';
@Component({
selector: 'app-select-controlled-open-state-demo',
imports: [
FormsModule,
AvSelectImports,
AvLabelComponent,
AvButtonComponent,
],
template: `<div class="space-y-4">
<div
av-select
class="w-64"
placeholder="Select one"
[(ngModel)]="value"
[(open)]="isOpen"
>
<label av-label>State</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
<div av-list-box-item id="florida" textValue="Florida">
Florida
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="delaware" textValue="Delaware">
Delaware
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="california" textValue="California">
California
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="texas" textValue="Texas">
Texas
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="new-york" textValue="New York">
New York
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="washington" textValue="Washington">
Washington
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
</div>
<button av-button (click)="isOpen = !isOpen">
{{ isOpen ? 'Close' : 'Open' }} Select
</button>
<p class="text-sm text-muted">Select is {{ isOpen ? 'open' : 'closed' }}</p>
</div>`,
})
export class SelectControlledOpenStateDemo {
value: string | null = null;
isOpen = false;
}Custom Indicator
Project custom content into av-select-indicator to replace the default chevron.
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
AvLabelComponent,
AvSelectImports,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-select-custom-indicator-demo',
imports: [
FormsModule,
AvSelectImports,
AvLabelComponent,
AppIconComponent,
],
template: `<div av-select class="w-64" placeholder="Select one" [(ngModel)]="value">
<label av-label>State</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator class="size-3">
<app-icon icon="solar:sort-vertical-linear" size="12" />
</span>
</button>
<av-select-popover>
<div av-list-box>
<div av-list-box-item id="florida" textValue="Florida">
Florida
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="delaware" textValue="Delaware">
Delaware
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="california" textValue="California">
California
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="texas" textValue="Texas">
Texas
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="new-york" textValue="New York">
New York
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="washington" textValue="Washington">
Washington
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
</div>`,
})
export class SelectCustomIndicatorDemo {
value: string | null = null;
}Custom Value
Project custom content into av-select-value to render a richer selected display. When nothing is selected, the placeholder is shown automatically.
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
AvAvatarImports,
AvDescriptionComponent,
AvLabelComponent,
AvSelectImports,
} from '@avesra/angular';
@Component({
selector: 'app-select-custom-value-demo',
imports: [
FormsModule,
AvSelectImports,
AvLabelComponent,
AvDescriptionComponent,
AvAvatarImports,
],
template: `<div av-select class="w-64" placeholder="Select a user" [(ngModel)]="selected">
<label av-label>User</label>
<button av-select-trigger>
<span av-select-value>
@if (selectedUser; as user) {
<div class="flex items-center gap-2">
<span av-avatar class="size-4" size="sm">
<img av-avatar-image [alt]="user.name" [src]="user.avatarUrl" />
<span av-avatar-fallback>{{ user.fallback }}</span>
</span>
<span>{{ user.name }}</span>
</div>
}
</span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
@for (user of users; track user.id) {
<div av-list-box-item [id]="user.id" [textValue]="user.name">
<span av-avatar size="sm">
<img av-avatar-image [alt]="user.name" [src]="user.avatarUrl" />
<span av-avatar-fallback>{{ user.fallback }}</span>
</span>
<div class="flex flex-col">
<label av-label>{{ user.name }}</label>
<p av-description>{{ user.email }}</p>
</div>
<span av-list-box-item-indicator></span>
</div>
}
</div>
</av-select-popover>
</div>`,
})
export class SelectCustomValueDemo {
selected: string | null = null;
readonly users = [
{
"avatarUrl": "/images/gradients/gradient-blue-cyan.png",
"email": "[email protected]",
"fallback": "B",
"id": "1",
"name": "Bob"
},
{
"avatarUrl": "/images/gradients/gradient-cyan-blue-purple.png",
"email": "[email protected]",
"fallback": "F",
"id": "2",
"name": "Fred"
},
{
"avatarUrl": "/images/gradients/gradient-purple-violet.png",
"email": "[email protected]",
"fallback": "M",
"id": "3",
"name": "Martha"
},
{
"avatarUrl": "/images/gradients/gradient-pink-magenta.png",
"email": "[email protected]",
"fallback": "J",
"id": "4",
"name": "John"
},
{
"avatarUrl": "/images/gradients/gradient-warm-orange-yellow-red.png",
"email": "[email protected]",
"fallback": "J",
"id": "5",
"name": "Jane"
}
];
get selectedUser() {
return this.users.find((user) => user.id === this.selected) ?? null;
}
}In Surface
When used inside a Surface component, use variant="secondary" to apply the lower emphasis variant suitable for surface backgrounds.
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import {
AvButtonComponent,
AvFieldErrorComponent,
AvFormComponent,
AvLabelComponent,
AvSelectImports,
AvSurfaceComponent,
} from '@avesra/angular';
@Component({
selector: 'app-select-in-surface-demo',
imports: [
ReactiveFormsModule,
AvFormComponent,
AvSurfaceComponent,
AvSelectImports,
AvLabelComponent,
AvFieldErrorComponent,
AvButtonComponent,
],
template: `<div av-surface class="w-[320px] rounded-3xl p-6">
<form
av-form
novalidate
class="flex w-full flex-col gap-4"
[formGroup]="form"
(ngSubmit)="onSubmit()"
>
<div
av-select
full-width
variant="secondary"
placeholder="Select one"
formControlName="state"
[invalid]="isStateInvalid()"
>
<label av-label required [invalid]="isStateInvalid()">State</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
<div av-list-box-item id="florida" textValue="Florida">
Florida
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="delaware" textValue="Delaware">
Delaware
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="california" textValue="California">
California
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="texas" textValue="Texas">
Texas
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="new-york" textValue="New York">
New York
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="washington" textValue="Washington">
Washington
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
<p av-field-error [visible]="isStateInvalid()">Please select an option.</p>
</div>
<div
av-select
full-width
variant="secondary"
placeholder="Select a country"
formControlName="country"
[invalid]="isCountryInvalid()"
>
<label av-label required [invalid]="isCountryInvalid()">Country</label>
<button av-select-trigger>
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
<div av-list-box-item id="usa" textValue="United States">
United States
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="canada" textValue="Canada">
Canada
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="mexico" textValue="Mexico">
Mexico
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="uk" textValue="United Kingdom">
United Kingdom
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="france" textValue="France">
France
<span av-list-box-item-indicator></span>
</div>
<div av-list-box-item id="germany" textValue="Germany">
Germany
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
<p av-field-error [visible]="isCountryInvalid()">Please select an option.</p>
</div>
<button av-button type="submit">Submit</button>
</form>
</div>`,
})
export class SelectInSurfaceDemo {
readonly form = new FormGroup({
state: new FormControl<string | null>(null, Validators.required),
country: new FormControl<string | null>(null, Validators.required),
});
isStateInvalid(): boolean {
const control = this.form.controls.state;
return control.invalid && (control.touched || control.dirty);
}
isCountryInvalid(): boolean {
const control = this.form.controls.country;
return control.invalid && (control.touched || control.dirty);
}
onSubmit(): void {
this.form.markAllAsTouched();
if (this.form.invalid) {
return;
}
alert('Form submitted successfully!');
}
}Styling
Passing Tailwind CSS classes
Pass utility classes on the Select hosts and nested List Box items.
<div av-select class="w-full" placeholder="Select an item">
<label av-label>State</label>
<button av-select-trigger class="rounded-lg border bg-surface p-2">
<span av-select-value></span>
<span av-select-indicator></span>
</button>
<av-select-popover>
<div av-list-box>
<div av-list-box-item id="1" textValue="Item 1" class="hover:bg-surface-secondary">
Item 1
<span av-list-box-item-indicator></span>
</div>
</div>
</av-select-popover>
</div>Customizing the component classes
To customize the Select classes, use the @layer components directive. Learn more.
@layer components {
.av-select {
@apply flex flex-col gap-1;
}
.av-select__trigger {
@apply rounded-lg border border-border bg-surface p-2;
}
.av-select__value {
@apply text-current;
}
.av-select__indicator {
@apply text-muted;
}
.av-select__popover {
@apply rounded-lg border border-border bg-surface p-2;
}
}Avesra follows the BEM methodology so component variants and states stay reusable and easy to customize.
CSS Classes
The Select component uses these CSS classes:
Base Classes
.av-select— Base select container.av-select__trigger— The button that triggers the select.av-select__value— The displayed value or placeholder.av-select__indicator— The dropdown indicator icon.av-select__popover— The floating popover panel
Variant Classes
.av-select--primary— Primary variant with shadow (default).av-select--secondary— Secondary variant without shadow, suitable for use in surfaces.av-select--full-width— Full width container.av-select__trigger--full-width— Full width trigger
State Classes
.av-select[data-invalid="true"]— Invalid state.av-select[data-disabled="true"]— Disabled select state.av-select__trigger[data-focus-visible="true"]— Focused trigger state.av-select__trigger[data-disabled="true"]— Disabled trigger state.av-select__value[data-placeholder="true"]— Placeholder state.av-select__indicator[data-open="true"]— Open indicator state.av-select__popover[data-entering="true"]/[data-exiting="true"]— Enter/exit animation
Interactive States
The component supports both CSS pseudo-classes and data attributes:
- Hover:
:hoveror[data-hovered="true"]on trigger - Focus:
:focus-visibleor[data-focus-visible="true"]on trigger - Disabled:
:disabledor[data-disabled="true"]on select / trigger - Open:
[data-open="true"]on indicator;aria-expandedon trigger
Accessibility
Select implements the ARIA combobox / listbox pattern via Angular CDK Overlay and provides:
- Trigger exposes
role="combobox",aria-haspopup="listbox", andaria-expanded - Full keyboard navigation (Enter/Space/Arrow keys to open, Escape to dismiss)
- Screen reader–friendly selection via the nested List Box
- Focus moves into the list on keyboard open and returns to the trigger on close
- Support for disabled and invalid states
- HTML form integration through
ControlValueAccessor(ngModel/formControlName)
Always pair the select with a visible av-label (or an accessible name) so assistive tech can identify the field.
API
Compound API across av-select , av-select-trigger , av-select-value , av-select-indicator , and av-select-popover . Options are composed with the independent List Box API inside the popover — do not treat List Box as a Select sub-part.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'primary' | 'secondary' | 'primary' | Visual variant. primary is the default style with shadow; secondary is lower emphasis without shadow, suitable for surfaces (av-select). |
full-width | boolean | false | Whether the select should take full width of its container (av-select). |
placeholder | string | 'Select…' | Temporary text shown when nothing is selected (av-select). |
selection-mode | 'single' | 'multiple' | 'single' | Whether single or multiple selection is enabled (av-select). |
disabled | boolean | false | Whether the select is disabled (av-select). |
invalid | boolean | false | Whether the select value is invalid (av-select). |
open | boolean | false | Controls whether the popover is open. Supports two-way binding with [(open)] (av-select). |
dismissable | boolean | true | Closes the popover when clicking outside (av-select). |
keyboard-dismiss-disabled | boolean | false | Disables closing via the Escape key (av-select). |
default-selected-keys | string[] | [] | Initial selected keys for uncontrolled usage (av-select). |
selectedKeys | string[] | [] | Selected keys (controlled). Supports two-way binding with [(selectedKeys)]. Also works with ngModel / formControlName via ControlValueAccessor (av-select). |
disabled | boolean | false | Disables the trigger (av-select-trigger). |
placeholder | string | — | Override placeholder text for the value slot (av-select-value). |
placement | AvSelectPlacement | 'bottom' | Preferred placement of the popover relative to the trigger (av-select-popover). |
offset | number | 8 | Distance between trigger and popover in pixels (av-select-popover). |
class | string | — | Extra classes applied to the overlay panel (av-select-popover). |
should-flip | boolean | true | Whether the popover can flip to fit the viewport (av-select-popover). |