AvesraAvesrabeta

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 cases
  • secondary — 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::hover or [data-hovered="true"] on trigger
  • Focus::focus-visible or [data-focus-visible="true"] on trigger
  • Disabled::disabled or [data-disabled="true"] on select / trigger
  • Open:[data-open="true"] on indicator; aria-expanded on trigger

Accessibility

Select implements the ARIA combobox / listbox pattern via Angular CDK Overlay and provides:

  • Trigger exposes role="combobox" , aria-haspopup="listbox" , and aria-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.

PropTypeDefaultDescription
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-widthbooleanfalseWhether the select should take full width of its container (av-select).
placeholderstring'Select…'Temporary text shown when nothing is selected (av-select).
selection-mode'single' | 'multiple''single'Whether single or multiple selection is enabled (av-select).
disabledbooleanfalseWhether the select is disabled (av-select).
invalidbooleanfalseWhether the select value is invalid (av-select).
openbooleanfalseControls whether the popover is open. Supports two-way binding with [(open)] (av-select).
dismissablebooleantrueCloses the popover when clicking outside (av-select).
keyboard-dismiss-disabledbooleanfalseDisables closing via the Escape key (av-select).
default-selected-keysstring[][]Initial selected keys for uncontrolled usage (av-select).
selectedKeysstring[][]Selected keys (controlled). Supports two-way binding with [(selectedKeys)]. Also works with ngModel / formControlName via ControlValueAccessor (av-select).
disabledbooleanfalseDisables the trigger (av-select-trigger).
placeholderstring—Override placeholder text for the value slot (av-select-value).
placementAvSelectPlacement'bottom'Preferred placement of the popover relative to the trigger (av-select-popover).
offsetnumber8Distance between trigger and popover in pixels (av-select-popover).
classstring—Extra classes applied to the overlay panel (av-select-popover).
should-flipbooleantrueWhether the popover can flip to fit the viewport (av-select-popover).

Made with ❤ by SyntaxHertz. Open source and available on GitHub.