Dropdown
A dropdown displays a list of actions or options that a user can choose from a trigger. Built with a floating popover, compound trigger / menu / item / section / indicator parts, and single or multiple selection.
Import
import { AvDropdownImports } from '@avesra/angular';Usage
import { Component } from '@angular/core';
import {
AvButtonComponent,
AvDropdownImports,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-dropdown-basic-demo',
imports: [AvDropdownImports, AvLabelComponent, AvButtonComponent],
template: `<av-dropdown>
<button av-button variant="secondary" aria-label="Menu" av-dropdown-trigger>Actions</button>
<av-dropdown-popover>
<div av-dropdown-menu (action)="onAction($event)">
<div av-menu-item id="new-file" textValue="New file">
<label av-label>New file</label>
</div>
<div av-menu-item id="copy-link" textValue="Copy link">
<label av-label>Copy link</label>
</div>
<div av-menu-item id="edit-file" textValue="Edit file">
<label av-label>Edit file</label>
</div>
<div av-menu-item id="delete-file" textValue="Delete file" variant="danger">
<label av-label>Delete file</label>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>`,
})
export class DropdownBasicDemo {
onAction(key: string): void {
console.log(`Selected: ${key}`);
}
}Anatomy
Import the Dropdown parts and compose them with attribute selectors.
<av-dropdown>
<button av-button av-dropdown-trigger></button>
<av-dropdown-popover>
<div av-dropdown-menu>
<div av-menu-item>
<label av-label></label>
<p av-description></p>
<kbd av-kbd></kbd>
<span av-menu-item-indicator></span>
</div>
<div av-separator></div>
<div av-menu-section>
<p></p>
<div av-menu-item></div>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>With Icons
import { Component } from '@angular/core';
import {
AvButtonComponent,
AvDropdownImports,
AvKbdImports,
AvLabelComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-dropdown-with-icons-demo',
imports: [AvDropdownImports, AvLabelComponent, AvKbdImports, AvButtonComponent, AppIconComponent],
template: `<av-dropdown>
<button av-button variant="secondary" aria-label="Menu" av-dropdown-trigger>Actions</button>
<av-dropdown-popover>
<div av-dropdown-menu (action)="onAction($event)">
<div av-menu-item id="new-file" textValue="New file">
<app-icon icon="solar:add-square-linear" size="16" class="shrink-0 text-muted" />
<label av-label>New file</label>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<span av-kbd-content>N</span>
</kbd>
</div>
<div av-menu-item id="open-file" textValue="Open file">
<app-icon icon="solar:folder-open-linear" size="16" class="shrink-0 text-muted" />
<label av-label>Open file</label>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<span av-kbd-content>O</span>
</kbd>
</div>
<div av-menu-item id="save-file" textValue="Save file">
<app-icon icon="solar:diskette-linear" size="16" class="shrink-0 text-muted" />
<label av-label>Save file</label>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<span av-kbd-content>S</span>
</kbd>
</div>
<div av-menu-item id="delete-file" textValue="Delete file" variant="danger">
<app-icon icon="solar:trash-bin-trash-linear" size="16" class="shrink-0 text-danger" />
<label av-label>Delete file</label>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<abbr av-kbd-abbr key-value="shift"></abbr>
<span av-kbd-content>D</span>
</kbd>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>`,
})
export class DropdownWithIconsDemo {
onAction(key: string): void {
console.log(`Selected: ${key}`);
}
}With Descriptions
import { Component } from '@angular/core';
import {
AvButtonComponent,
AvDescriptionComponent,
AvDropdownImports,
AvKbdImports,
AvLabelComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-dropdown-with-descriptions-demo',
imports: [AvDropdownImports, AvLabelComponent, AvDescriptionComponent, AvKbdImports, AvButtonComponent, AppIconComponent],
template: `<av-dropdown>
<button av-button variant="secondary" aria-label="Menu" av-dropdown-trigger>Actions</button>
<av-dropdown-popover>
<div av-dropdown-menu (action)="onAction($event)">
<div av-menu-item id="new-file" textValue="New file">
<div class="flex h-8 items-start justify-center pt-px">
<app-icon icon="solar:add-square-linear" size="16" class="shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<label av-label>New file</label>
<p av-description>Create a new file</p>
</div>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<span av-kbd-content>N</span>
</kbd>
</div>
<div av-menu-item id="open-file" textValue="Open file">
<div class="flex h-8 items-start justify-center pt-px">
<app-icon icon="solar:folder-open-linear" size="16" class="shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<label av-label>Open file</label>
<p av-description>Open an existing file</p>
</div>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<span av-kbd-content>O</span>
</kbd>
</div>
<div av-menu-item id="save-file" textValue="Save file">
<div class="flex h-8 items-start justify-center pt-px">
<app-icon icon="solar:diskette-linear" size="16" class="shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<label av-label>Save file</label>
<p av-description>Save the current file</p>
</div>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<span av-kbd-content>S</span>
</kbd>
</div>
<div av-menu-item id="delete-file" textValue="Delete file" variant="danger">
<div class="flex h-8 items-start justify-center pt-px">
<app-icon icon="solar:trash-bin-trash-linear" size="16" class="shrink-0 text-danger" />
</div>
<div class="flex flex-col">
<label av-label>Delete file</label>
<p av-description>Move to trash</p>
</div>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<abbr av-kbd-abbr key-value="shift"></abbr>
<span av-kbd-content>D</span>
</kbd>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>`,
})
export class DropdownWithDescriptionsDemo {
onAction(key: string): void {
console.log(`Selected: ${key}`);
}
}With Disabled Items
import { Component } from '@angular/core';
import {
AvButtonComponent,
AvDescriptionComponent,
AvDropdownImports,
AvKbdImports,
AvLabelComponent,
AvSeparatorImports,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-dropdown-with-disabled-items-demo',
imports: [AvDropdownImports, AvLabelComponent, AvDescriptionComponent, AvKbdImports, AvSeparatorImports, AvButtonComponent, AppIconComponent],
template: `<av-dropdown>
<button av-button variant="secondary" icon-only aria-label="Menu" av-dropdown-trigger>
<app-icon icon="solar:hamburger-menu-linear" size="16" />
</button>
<av-dropdown-popover>
<div class="min-w-[220px]">
<div av-dropdown-menu (action)="onAction($event)">
<div av-menu-section>
<p class="px-2.5 py-1 text-xs font-medium text-muted">Actions</p>
<div av-menu-item id="new-file" textValue="New file">
<div class="flex h-8 items-start justify-center pt-px">
<app-icon icon="solar:add-square-linear" size="16" class="shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<label av-label>New file</label>
<p av-description>Create a new file</p>
</div>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<span av-kbd-content>N</span>
</kbd>
</div>
<div av-menu-item id="edit-file" textValue="Edit file">
<div class="flex h-8 items-start justify-center pt-px">
<app-icon icon="solar:pen-linear" size="16" class="shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<label av-label>Edit file</label>
<p av-description>Make changes</p>
</div>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<span av-kbd-content>E</span>
</kbd>
</div>
</div>
<div av-separator></div>
<div av-menu-section>
<p class="px-2.5 py-1 text-xs font-medium text-muted">Danger zone</p>
<div av-menu-item id="delete-file" textValue="Delete file" variant="danger" disabled>
<div class="flex h-8 items-start justify-center pt-px">
<app-icon icon="solar:trash-bin-trash-linear" size="16" class="shrink-0 text-danger" />
</div>
<div class="flex flex-col">
<label av-label>Delete file</label>
<p av-description>Move to trash</p>
</div>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<abbr av-kbd-abbr key-value="shift"></abbr>
<span av-kbd-content>D</span>
</kbd>
</div>
</div>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>`,
})
export class DropdownWithDisabledItemsDemo {
onAction(key: string): void {
console.log(`Selected: ${key}`);
}
}With Sections
import { Component } from '@angular/core';
import {
AvButtonComponent,
AvDescriptionComponent,
AvDropdownImports,
AvKbdImports,
AvLabelComponent,
AvSeparatorImports,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-dropdown-with-sections-demo',
imports: [AvDropdownImports, AvLabelComponent, AvDescriptionComponent, AvKbdImports, AvSeparatorImports, AvButtonComponent, AppIconComponent],
template: `<av-dropdown>
<button av-button variant="secondary" icon-only aria-label="Menu" av-dropdown-trigger>
<app-icon icon="solar:menu-dots-linear" size="16" />
</button>
<av-dropdown-popover>
<div av-dropdown-menu (action)="onAction($event)">
<div av-menu-section>
<p class="px-2.5 py-1 text-xs font-medium text-muted">Actions</p>
<div av-menu-item id="new-file" textValue="New file">
<div class="flex h-8 items-start justify-center pt-px">
<app-icon icon="solar:add-square-linear" size="16" class="shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<label av-label>New file</label>
<p av-description>Create a new file</p>
</div>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<span av-kbd-content>N</span>
</kbd>
</div>
<div av-menu-item id="edit-file" textValue="Edit file">
<div class="flex h-8 items-start justify-center pt-px">
<app-icon icon="solar:pen-linear" size="16" class="shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<label av-label>Edit file</label>
<p av-description>Make changes</p>
</div>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<span av-kbd-content>E</span>
</kbd>
</div>
</div>
<div av-separator></div>
<div av-menu-section>
<p class="px-2.5 py-1 text-xs font-medium text-muted">Danger zone</p>
<div av-menu-item id="delete-file" textValue="Delete file" variant="danger">
<div class="flex h-8 items-start justify-center pt-px">
<app-icon icon="solar:trash-bin-trash-linear" size="16" class="shrink-0 text-danger" />
</div>
<div class="flex flex-col">
<label av-label>Delete file</label>
<p av-description>Move to trash</p>
</div>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<abbr av-kbd-abbr key-value="shift"></abbr>
<span av-kbd-content>D</span>
</kbd>
</div>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>`,
})
export class DropdownWithSectionsDemo {
onAction(key: string): void {
console.log(`Selected: ${key}`);
}
}With Multiple Selection
Set selection-mode="multiple" to let users toggle several items without closing the menu. Project a custom av-checkbox into the indicator and bind it with #item="avMenuItem" and [selected]="item.isSelected()".
import { Component, signal } from '@angular/core';
import {
AvButtonComponent,
AvCheckboxImports,
AvDropdownImports,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-dropdown-multiple-selection-demo',
imports: [AvDropdownImports, AvCheckboxImports, AvLabelComponent, AvButtonComponent],
template: `<av-dropdown>
<button av-button variant="secondary" av-dropdown-trigger>Preferred fruits</button>
<av-dropdown-popover>
<div class="min-w-[256px]">
<div av-dropdown-menu selection-mode="multiple" [(selectedKeys)]="selected">
<div av-menu-section>
<p class="px-2.5 py-1 text-xs font-medium text-muted">Select fruits</p>
<div av-menu-item #apple="avMenuItem" id="multi-apple" textValue="Apple">
<span av-menu-item-indicator>
<div av-checkbox class="pointer-events-none" [selected]="apple.isSelected()" aria-hidden="true">
<span av-checkbox-control>
<span av-checkbox-indicator></span>
</span>
</div>
</span>
<label av-label>Apple</label>
</div>
<div av-menu-item #banana="avMenuItem" id="multi-banana" textValue="Banana">
<span av-menu-item-indicator>
<div av-checkbox class="pointer-events-none" [selected]="banana.isSelected()" aria-hidden="true">
<span av-checkbox-control>
<span av-checkbox-indicator></span>
</span>
</div>
</span>
<label av-label>Banana</label>
</div>
<div av-menu-item #cherry="avMenuItem" id="multi-cherry" textValue="Cherry">
<span av-menu-item-indicator>
<div av-checkbox class="pointer-events-none" [selected]="cherry.isSelected()" aria-hidden="true">
<span av-checkbox-control>
<span av-checkbox-indicator></span>
</span>
</div>
</span>
<label av-label>Cherry</label>
</div>
</div>
<div av-menu-item #orange="avMenuItem" id="multi-orange" textValue="Orange">
<span av-menu-item-indicator>
<div av-checkbox class="pointer-events-none" [selected]="orange.isSelected()" aria-hidden="true">
<span av-checkbox-control>
<span av-checkbox-indicator></span>
</span>
</div>
</span>
<label av-label>Orange</label>
</div>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>`,
})
export class DropdownMultipleSelectionDemo {
readonly selected = signal<string[]>(['multi-apple', 'multi-banana']);
}Controlled
Selected: bold
import { Component, computed, signal } from '@angular/core';
import {
AvButtonComponent,
AvDropdownImports,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-dropdown-controlled-demo',
imports: [AvDropdownImports, AvLabelComponent, AvButtonComponent],
template: `<div class="flex min-w-sm flex-col items-center justify-center gap-4">
<p class="text-sm text-muted">Selected: {{ selectedLabel() }}</p>
<av-dropdown>
<button av-button variant="secondary" aria-label="Menu" av-dropdown-trigger>Actions</button>
<av-dropdown-popover>
<div av-dropdown-menu selection-mode="multiple" [(selectedKeys)]="selected">
<div av-menu-item id="bold" textValue="Bold">
<label av-label>Bold</label>
<span av-menu-item-indicator></span>
</div>
<div av-menu-item id="italic" textValue="Italic">
<label av-label>Italic</label>
<span av-menu-item-indicator></span>
</div>
<div av-menu-item id="underline" textValue="Underline">
<label av-label>Underline</label>
<span av-menu-item-indicator></span>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>
</div>`,
})
export class DropdownControlledDemo {
readonly selected = signal<string[]>(['bold']);
readonly selectedLabel = computed(() => {
const keys = this.selected();
return keys.length > 0 ? keys.join(', ') : 'None';
});
}Controlled Open State
Dropdown is: closed
import { Component, signal } from '@angular/core';
import {
AvButtonComponent,
AvDropdownImports,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-dropdown-controlled-open-state-demo',
imports: [AvDropdownImports, AvLabelComponent, AvButtonComponent],
template: `<div class="flex min-w-sm flex-col items-center justify-center gap-4">
<p class="text-sm text-muted">
Dropdown is: <strong>{{ open() ? 'open' : 'closed' }}</strong>
</p>
<av-dropdown [(open)]="open">
<button av-button variant="secondary" aria-label="Menu" av-dropdown-trigger>Actions</button>
<av-dropdown-popover>
<div av-dropdown-menu>
<div av-menu-item id="new-file" textValue="New file">
<label av-label>New file</label>
</div>
<div av-menu-item id="open-file" textValue="Open file">
<label av-label>Open file</label>
</div>
<div av-menu-item id="save-file" textValue="Save file">
<label av-label>Save file</label>
</div>
<div av-menu-item id="delete-file" textValue="Delete file" variant="danger">
<label av-label>Delete file</label>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>
</div>`,
})
export class DropdownControlledOpenStateDemo {
readonly open = signal(false);
}With Single Selection
import { Component, signal } from '@angular/core';
import {
AvButtonComponent,
AvDropdownImports,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-dropdown-single-selection-demo',
imports: [AvDropdownImports, AvLabelComponent, AvButtonComponent],
template: `<av-dropdown>
<button av-button variant="secondary" aria-label="Menu" av-dropdown-trigger>Fruit</button>
<av-dropdown-popover>
<div class="min-w-[256px]">
<div av-dropdown-menu selection-mode="single" [(selectedKeys)]="selected">
<div av-menu-section>
<p class="px-2.5 py-1 text-xs font-medium text-muted">Select a fruit</p>
<div av-menu-item id="apple" textValue="Apple">
<span av-menu-item-indicator></span>
<label av-label>Apple</label>
</div>
<div av-menu-item id="banana" textValue="Banana">
<span av-menu-item-indicator></span>
<label av-label>Banana</label>
</div>
<div av-menu-item id="cherry" textValue="Cherry">
<span av-menu-item-indicator></span>
<label av-label>Cherry</label>
</div>
</div>
<div av-menu-item id="orange" textValue="Orange">
<span av-menu-item-indicator></span>
<label av-label>Orange</label>
</div>
<div av-menu-item id="pear" textValue="Pear">
<span av-menu-item-indicator></span>
<label av-label>Pear</label>
</div>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>`,
})
export class DropdownSingleSelectionDemo {
readonly selected = signal<string[]>(['apple']);
}Single With Custom Indicator
import { Component, signal } from '@angular/core';
import {
AvButtonComponent,
AvDropdownImports,
AvLabelComponent,
AvRadioImports,
} from '@avesra/angular';
@Component({
selector: 'app-dropdown-single-custom-indicator-demo',
imports: [AvDropdownImports, AvRadioImports, AvLabelComponent, AvButtonComponent],
template: `<av-dropdown>
<button av-button variant="secondary" aria-label="Menu" av-dropdown-trigger>Fruits</button>
<av-dropdown-popover>
<div class="min-w-[256px]">
<div av-dropdown-menu selection-mode="single" [(selectedKeys)]="selected">
<div av-menu-section>
<p class="px-2.5 py-1 text-xs font-medium text-muted">Select a fruit</p>
<div av-menu-item #apple="avMenuItem" id="apple" textValue="Apple">
<span av-menu-item-indicator>
<div av-radio class="pointer-events-none" [selected]="apple.isSelected()" aria-hidden="true">
<span av-radio-control>
<span av-radio-indicator></span>
</span>
</div>
</span>
<label av-label>Apple</label>
</div>
<div av-menu-item #banana="avMenuItem" id="banana" textValue="Banana">
<span av-menu-item-indicator>
<div av-radio class="pointer-events-none" [selected]="banana.isSelected()" aria-hidden="true">
<span av-radio-control>
<span av-radio-indicator></span>
</span>
</div>
</span>
<label av-label>Banana</label>
</div>
<div av-menu-item #cherry="avMenuItem" id="cherry" textValue="Cherry">
<span av-menu-item-indicator>
<div av-radio class="pointer-events-none" [selected]="cherry.isSelected()" aria-hidden="true">
<span av-radio-control>
<span av-radio-indicator></span>
</span>
</div>
</span>
<label av-label>Cherry</label>
</div>
</div>
<div av-menu-item #orange="avMenuItem" id="orange" textValue="Orange">
<span av-menu-item-indicator>
<div av-radio class="pointer-events-none" [selected]="orange.isSelected()" aria-hidden="true">
<span av-radio-control>
<span av-radio-indicator></span>
</span>
</div>
</span>
<label av-label>Orange</label>
</div>
<div av-menu-item #pear="avMenuItem" id="pear" textValue="Pear">
<span av-menu-item-indicator>
<div av-radio class="pointer-events-none" [selected]="pear.isSelected()" aria-hidden="true">
<span av-radio-control>
<span av-radio-indicator></span>
</span>
</div>
</span>
<label av-label>Pear</label>
</div>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>`,
})
export class DropdownSingleCustomIndicatorDemo {
readonly selected = signal<string[]>(['apple']);
}With Keyboard Shortcuts
Compose kbd[av-kbd] inside av-menu-item with class="ms-auto" to show trailing keyboard shortcuts.
import { Component } from '@angular/core';
import {
AvButtonComponent,
AvDropdownImports,
AvKbdImports,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-dropdown-with-keyboard-shortcuts-demo',
imports: [
AvDropdownImports,
AvLabelComponent,
AvKbdImports,
AvButtonComponent,
],
template: `<av-dropdown>
<button av-button variant="secondary" aria-label="Menu" av-dropdown-trigger>Actions</button>
<av-dropdown-popover>
<div av-dropdown-menu (action)="onAction($event)">
<div av-menu-item id="new" textValue="New">
<label av-label>New</label>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<span av-kbd-content>N</span>
</kbd>
</div>
<div av-menu-item id="open" textValue="Open">
<label av-label>Open</label>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<span av-kbd-content>O</span>
</kbd>
</div>
<div av-menu-item id="save" textValue="Save">
<label av-label>Save</label>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<span av-kbd-content>S</span>
</kbd>
</div>
<div av-menu-item id="delete" textValue="Delete" variant="danger">
<label av-label>Delete</label>
<kbd av-kbd variant="light" class="ms-auto">
<abbr av-kbd-abbr key-value="command"></abbr>
<abbr av-kbd-abbr key-value="shift"></abbr>
<span av-kbd-content>D</span>
</kbd>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>`,
})
export class DropdownWithKeyboardShortcutsDemo {
onAction(key: string): void {
console.log(`Selected: ${key}`);
}
}Custom Trigger
import { Component } from '@angular/core';
import {
AvAvatarImports,
AvDropdownImports,
AvLabelComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-dropdown-custom-trigger-demo',
imports: [AvDropdownImports, AvLabelComponent, AvAvatarImports, AppIconComponent],
template: `<av-dropdown>
<button
av-dropdown-trigger
custom-trigger
type="button"
class="rounded-full outline-none"
aria-label="User menu"
>
<span av-avatar>
<img av-avatar-image alt="Junior Garcia" src="/images/gradients/gradient-warm-orange-yellow-red.png" />
<span av-avatar-fallback delay-ms="600">JD</span>
</span>
</button>
<av-dropdown-popover>
<div class="px-3 pt-3 pb-1">
<div class="flex items-center gap-2">
<span av-avatar size="sm">
<img av-avatar-image alt="Jane" src="/images/gradients/gradient-warm-orange-yellow-red.png" />
<span av-avatar-fallback delay-ms="600">JD</span>
</span>
<div class="flex flex-col gap-0">
<p class="text-sm leading-5 font-medium">Jane Doe</p>
<p class="text-xs leading-none text-muted">jane@example.com</p>
</div>
</div>
</div>
<div av-dropdown-menu>
<div av-menu-item id="dashboard" textValue="Dashboard">
<label av-label>Dashboard</label>
</div>
<div av-menu-item id="profile" textValue="Profile">
<label av-label>Profile</label>
</div>
<div av-menu-item id="settings" textValue="Settings">
<div class="flex w-full items-center justify-between gap-2">
<label av-label>Settings</label>
<app-icon icon="solar:settings-linear" size="14" class="text-muted" />
</div>
</div>
<div av-menu-item id="new-project" textValue="New project">
<div class="flex w-full items-center justify-between gap-2">
<label av-label>Create Team</label>
<app-icon icon="solar:users-group-rounded-linear" size="14" class="text-muted" />
</div>
</div>
<div av-menu-item id="logout" textValue="Logout" variant="danger">
<div class="flex w-full items-center justify-between gap-2">
<label av-label>Log Out</label>
<app-icon icon="solar:logout-2-linear" size="14" class="text-danger" />
</div>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>`,
})
export class DropdownCustomTriggerDemo {}Styling
Pass utility classes on the host elements, or override BEM classes in @layer components.
CSS Classes
.av-dropdown— Base dropdown container.av-dropdown__trigger— The element that toggles the popover.av-dropdown__popover— The floating popover panel.av-dropdown__menu— The menu container inside the popover
Menu Classes
Dropdown composes the shared av-menu-item and av-menu-section primitives, so their classes are also available for customization:
.av-menu-item— Individual menu item container.av-menu-item--danger— Danger item variant.av-menu-item__indicator— Selection indicator (checkmark or dot)[data-slot="menu-item-indicator--checkmark"]— Checkmark indicator SVG[data-slot="menu-item-indicator--dot"]— Dot indicator SVG.av-menu-section— Section group (role="group")[data-slot="separator"]— Separator elements within the menu
Interactive States
Trigger and item styles support both CSS pseudo-classes and data attributes:
- Hover:
:hoveror[data-hovered="true"]on items - Pressed:
:activeor[data-pressed="true"] - Focus:
:focus-visibleor[data-focus-visible="true"] - Selected:
[data-selected="true"]or[aria-checked="true"] - Disabled:
[data-disabled="true"]or[aria-disabled="true"] - Entering/exiting popover:
[data-entering="true"]/[data-exiting="true"] - Selection mode:
.av-dropdown__menu[data-selection-mode="single"]/[data-selection-mode="multiple"]
Examples
Basic Usage
<av-dropdown>
<button av-button variant="secondary" aria-label="Menu" av-dropdown-trigger>
Actions
</button>
<av-dropdown-popover>
<div av-dropdown-menu (action)="onAction($event)">
<div av-menu-item id="new-file" textValue="New file">
<label av-label>New file</label>
</div>
<div av-menu-item id="open-file" textValue="Open file">
<label av-label>Open file</label>
</div>
<div av-menu-item id="delete-file" textValue="Delete file" variant="danger">
<label av-label>Delete file</label>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>Controlled Selection
Drive selection from a signal<string[]> and bind it with [(selectedKeys)] for two-way sync between the menu and outside state.
<av-dropdown>
<button av-button variant="secondary" aria-label="Format" av-dropdown-trigger>
Format
</button>
<av-dropdown-popover>
<div
av-dropdown-menu
selection-mode="multiple"
[(selectedKeys)]="selected"
>
<div av-menu-item id="bold" textValue="Bold">
<label av-label>Bold</label>
<span av-menu-item-indicator></span>
</div>
<div av-menu-item id="italic" textValue="Italic">
<label av-label>Italic</label>
<span av-menu-item-indicator></span>
</div>
</div>
</av-dropdown-popover>
</av-dropdown>Accessibility
Dropdown implements the ARIA menu pattern via Angular CDK Menu and provides:
- Full keyboard navigation (arrow keys, Enter/Space, Escape to dismiss)
- Typeahead search using each item's
textValue aria-haspopup="menu"andaria-expandedautomatically managed on the trigger- Focus is moved to the first item on open and returned to the trigger on close
- Proper
aria-checked/aria-disableditem states for selection - Section containers expose
role="group"; indicators arearia-hidden
Always provide an accessible label for the trigger — either visible text or aria-label when using an icon-only button.
API
Compound API across av-dropdown, av-dropdown-trigger, av-dropdown-popover, av-dropdown-menu, av-menu-item, av-menu-item-indicator, and av-menu-section (no inputs). See also List Box, which shares the same item and section composition for non-overlay selection lists.
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | false | Controls whether the dropdown is open. Supports two-way binding with [(open)] (av-dropdown). |
dismissable | boolean | true | Closes the dropdown when clicking outside (av-dropdown). |
keyboard-dismiss-disabled | boolean | false | Disables closing via the Escape key (av-dropdown). |
disabled | boolean | false | Disables the trigger (av-dropdown-trigger). |
custom-trigger | boolean | false | Applies trigger styles to non-button elements (av-dropdown-trigger). |
placement | 'top' | 'top start' | 'top end' | 'bottom' | 'bottom start' | 'bottom end' | 'left' | 'left top' | 'left bottom' | 'right' | 'right top' | 'right bottom' | 'start' | 'start top' | 'start bottom' | 'end' | 'end top' | 'end bottom' | 'bottom' | Preferred placement relative to the trigger (av-dropdown-popover). |
offset | number | 8 | Distance between trigger and menu in pixels (av-dropdown-popover). |
class | string | — | Extra classes applied to the overlay panel (av-dropdown-popover). |
should-flip | boolean | true | Whether the menu can flip to fit the viewport (av-dropdown-popover). |
selection-mode | 'none' | 'single' | 'multiple' | 'none' | How menu items can be selected (av-dropdown-menu). |
selectedKeys | string[] | [] | Selected item ids. Supports two-way binding with [(selectedKeys)] (av-dropdown-menu). |
disabled | boolean | false | Disables all menu items (av-dropdown-menu). |
action | EventEmitter<string> | — | Emits the item id when activated (av-dropdown-menu). |
id | string | — | Required unique item id (av-menu-item). |
textValue | string | — | Accessible label used for typeahead search (av-menu-item). |
variant | 'default' | 'danger' | 'default' | Visual style variant for the item (av-menu-item). |
disabled | boolean | false | Disables a single item (av-menu-item). |
class | string | — | Extra classes merged onto the menu item host (av-menu-item). |
type | 'checkmark' | 'dot' | 'checkmark' | Selection indicator style (av-menu-item-indicator). |