ComponentCDK OverlayUpdated in v15

Dropdown

A menu that opens from any trigger.

Installation

Import the class, and @use the entry point in your global styles: without it the component renders unstyled.

import { Component } from '@angular/core';
import { WrDropdown, WrDropdownItem, WrDropdownMenu } from 'ngwr/dropdown';

@Component({ imports: [WrDropdown, WrDropdownItem, WrDropdownMenu] })
export class MyComponent {}

Basic usage

Point [wrDropdown] at a <wr-dropdown-menu>. A click on an item does not close the menu; call close() on the trigger.

<!-- Items do not close the menu: d.close() does. -->
<button wr-btn type="button" [wrDropdown]="menu" #d="wrDropdown">Actions</button>

<wr-dropdown-menu #menu>
  <wr-dropdown-item icon="copy-outline" (click)="copy(); d.close()">Copy</wr-dropdown-item>
  <wr-dropdown-item icon="download" (click)="download(); d.close()">Download</wr-dropdown-item>
  <wr-dropdown-item icon="trash" (click)="remove(); d.close()">Delete</wr-dropdown-item>
</wr-dropdown-menu>

Positions

Set position. A menu that does not fit flips to the other side.

<button wr-btn type="button" [wrDropdown]="menu" position="top-start">Top start</button>

Arrow

On by default. Set [arrow]="false" on a menu with no padding or with clipped overflow.

<!-- On by default -->
<button wr-btn type="button" [wrDropdown]="menu">With arrow</button>

<button wr-btn type="button" [wrDropdown]="menu" [arrow]="false">Without arrow</button>

Hover trigger

Closes when the pointer leaves both the trigger and the menu.

<button wr-btn type="button" [wrDropdown]="menu" trigger="hover">Hover me</button>

Disabled item

<wr-dropdown-item icon="cog" disabled>Disabled item</wr-dropdown-item>

Template reference

The trigger exports wrDropdown, so the template can open, close or read the menu.

<wr-btn [wrDropdown]="menu" #actions="wrDropdown">Actions</wr-btn>
<wr-dropdown-menu #menu>
  <wr-dropdown-item>Copy</wr-dropdown-item>
</wr-dropdown-menu>

<!-- Anywhere else in the same template -->
<wr-btn (click)="actions.toggle()">Toggle from here</wr-btn>
@if (actions.isOpen()) {
  <span>The menu is up.</span>
}

The trigger keeps its own id

Set your own id for tests: the generated one changes between pages.

<!-- The menu gets aria-labelledby="row-actions" -->
<button wr-btn type="button" id="row-actions" [wrDropdown]="menu">Actions</button>

<!-- Bound ids work too -->
<button wr-btn type="button" [id]="'actions-' + row.id" [wrDropdown]="menu">Actions</button>

Scrolling containers

Add cdkScrollable to your own scrolling box, or the menu drifts from its trigger. Every anchored ngwr overlay needs it.

import { CdkScrollable } from '@angular/cdk/scrolling';

@Component({
  imports: [CdkScrollable, WrDropdown, WrDropdownMenu, WrDropdownItem],
  template: `
    <div class="side-panel" cdkScrollable>
      <button wr-btn type="button" [wrDropdown]="menu">Actions</button>
    </div>
  `,
})
export class PanelComponent {}

Directive API

NameDescriptionTypeDefault
wrDropdownrequiredMenu to open. Pass a <wr-dropdown-menu> template reference.WrDropdownMenu—
triggerHow the menu opens.WrDropdownTrigger'click'
positionWhere the menu anchors relative to the trigger. A placement that does not fit flips to the opposite side (a left / right menu may also drop below or above, and then stays there until it closes), and the pane's wr-dropdown-overlay--<placement> class names the side it actually took.WrDropdownPosition'bottom-start'
arrowDraw a small arrow on the menu's edge, pointing at the trigger — the one wr-popover and wr-popconfirm draw, from the same shared rule. It follows the placement the menu actually took, so a flipped menu points the right way, and it widens the gap to the trigger from 0.25rem to popover's 0.5rem so the tip does not touch the control. Turn it off on a menu with no padding of its own (--wr-dropdown-padding: 0) or one that clips its overflow: the square reaches about 3px inside the menu's border, over the first row, and overflow cuts its tip off. Read when the menu opens.booleantrue
panelClassExtra CSS classes for the menu's overlay pane. The pane is appended to the overlay container, not to this component, so nothing in the consumer's own template encloses it and no descendant rule written around the trigger can reach it. This input is the only per-instance handle on it. A space-separated string works as well as an array.WrClassInputnull
(opened)Fires after the menu opens.void—
(closed)Fires after the menu closes.void—

Item API

NameDescriptionTypeDefault
iconOptional leading icon name.WrIconName | nullnull
disabledDisable interaction (suppresses pointer + keyboard).booleanfalse

CSS variables

Set these on the component's selector or a wrapper, not on :root: the defaults are declared on ngwr/dropdown's own element.

VariableDefaultDeclared on
--wr-dropdown-bgvar(--wr-color-surface).wr-dropdown-menu
--wr-dropdown-bordervar(--wr-border-base).wr-dropdown-menu
--wr-dropdown-font-familyvar(--wr-font-family-base).wr-dropdown-menu
--wr-dropdown-item-bg-hovervar(--wr-color-hover).wr-dropdown-item
--wr-dropdown-item-colorvar(--wr-color-on-surface).wr-dropdown-item
--wr-dropdown-item-font-sizevar(--wr-text-sm).wr-dropdown-item
--wr-dropdown-item-gapvar(--wr-space-sm).wr-dropdown-item
--wr-dropdown-item-line-heightvar(--wr-text-sm-leading).wr-dropdown-item
--wr-dropdown-item-padding-xvar(--wr-space-md).wr-dropdown-item
--wr-dropdown-item-padding-yvar(--wr-space-sm).wr-dropdown-item
--wr-dropdown-item-radiusvar(--wr-border-radius-sm).wr-dropdown-item
--wr-dropdown-min-width10rem.wr-dropdown-menu
--wr-dropdown-paddingvar(--wr-space-xs).wr-dropdown-menu
--wr-dropdown-radiusvar(--wr-border-radius-base).wr-dropdown-menu
--wr-dropdown-shadowvar(--wr-shadow-overlay).wr-dropdown-menu

Dependencies

Angular CDKoverlay, portal
Built onIcon, Overlay
Used byTable, Window

See also