ComponentCDK OverlayUpdated in v15

Drawer

A side panel that slides in from any edge of the screen.

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 { WR_DRAWER_DATA, WrDrawer, WrDrawerClose, WrDrawerContent, WrDrawerFooter, WrDrawerManager, WrDrawerRef, WrDrawerTitle } from 'ngwr/drawer';

@Component({ imports: [WrDrawer, WrDrawerClose, WrDrawerContent, WrDrawerFooter, WrDrawerTitle] })
export class MyComponent {}

Basic usage

Bind [(open)]; position picks the edge.

<wr-btn (click)="open.set(true)">Open</wr-btn>

<wr-drawer [(open)]="open" position="right" width="22rem">
  <h2 wrDrawerTitle>Settings</h2>
  <div wrDrawerContent>…</div>
  <div wrDrawerFooter>
    <wr-btn wrDrawerClose>Close</wr-btn>
    <wr-btn color="primary" wrDrawerClose>Save</wr-btn>
  </div>
</wr-drawer>
Open drawer

Without a backdrop

Since v15 a drawer with hasBackdrop off is non-modal: the page stays clickable, scrollable and reachable with Tab.

<wr-drawer [(open)]="open" [hasBackdrop]="false" width="18rem">
  <h2 wrDrawerTitle>Details</h2>
  <div wrDrawerContent>…</div>
</wr-drawer>
Open panel

Focus still moves into the panel on open. Escape closes it unless you have moved on to the page, and closing returns focus to the opener unless you have moved on likewise.

Opening a drawer from code

WrDrawerManager.open() opens any component as a drawer and returns its close result. The content injects WR_DRAWER_DATA and WrDrawerRef.

import { WrDrawerManager } from 'ngwr/drawer';

export class Inbox {
  private readonly drawers = inject(WrDrawerManager);

  async openChat(threadId: string): Promise<void> {
    const ref = this.drawers.open<ChatComponent, string, ChatData>(ChatComponent, {
      data: { thread: threadId },
      position: 'right',
      width: '24rem',
    });

    const sent = await ref.awaitClose(); // string | undefined
  }
}
import { WR_DRAWER_DATA, WrDrawerRef } from 'ngwr/drawer';

@Component({...})
export class ChatComponent {
  protected readonly data = inject<ChatData>(WR_DRAWER_DATA);
  private readonly ref = inject<WrDrawerRef<ChatComponent, string>>(WrDrawerRef);

  send(): void {
    this.store.send(this.draft());
    this.ref.close('sent');
  }
}
Open chat drawer Last result: —
drawers.open(ChatComponent, { data, position: 'right', width: '24rem' })

Drawer API

NameDescriptionTypeDefault
openOpen state. Two-way bindable.booleanfalse
positionSide the drawer slides in from.'left' | 'right' | 'top' | 'bottom''right'
widthWidth when position is left/right.string'20rem'
heightHeight when position is top/bottom.string'16rem'
hasBackdropShow the dimming backdrop and make the drawer modal. false gives a non-modal panel.booleantrue
closeOnBackdropClickClose when backdrop is clicked.booleantrue
closeOnEscapeClose on Escape.booleantrue
closableShow the built-in dismiss (×) in the top-right corner.booleantrue
closeLabelAccessible name for the dismiss button. Falls back to the drawer.close catalog key.string | nullnull

Focus and lifetime

Mark the field to focus with cdkFocusInitial. Without it, <wr-drawer> focuses its ✕ and WrDrawerManager the first control.

<wr-drawer [(open)]="open" position="right">
  <h2 wrDrawerTitle>Filters</h2>
  <div wrDrawerContent>
    <wr-form-field label="Search">
      <input wrInput cdkFocusInitial [value]="query()" (input)="onQuery($event)" />
    </wr-form-field>
  </div>
</wr-drawer>

A drawer from WrDrawerManager outlives its opener: close the ref in DestroyRef.onDestroy. <wr-drawer> closes with its view.

Service API

NameDescriptionTypeDefault
open(component, options?)Opens a component as a drawer. Returns a WrDrawerRef.(component, WrDrawerOptions) => WrDrawerRef—

WrDrawerOptions

The component inputs, plus data and closeOnNavigation.

NameDescriptionTypeDefault
dataPayload exposed to the content via WR_DRAWER_DATA.D—
closeOnNavigationClose the drawer when the router navigates.booleantrue
positionSide the drawer slides in from.'left' | 'right' | 'top' | 'bottom''right'
widthWidth when position is left/right.string'20rem'
heightHeight when position is top/bottom.string'16rem'
maxHeightHeight cap for top/bottom positions.string | nullnull
roundedRound the leading corners.booleanfalse
safeAreaPad the trailing edge with the safe-area inset.booleanfalse
hasBackdropShow the dimming backdrop and make the drawer modal. false gives a non-modal panel.booleantrue
closeOnBackdropClickClose when backdrop is clicked.booleantrue
closeOnEscapeClose on Escape.booleantrue
closableShow the built-in dismiss (×) in the top-right corner.booleantrue
panelClassExtra class(es) on the panel.WrClassInputnull
closeLabelAccessible name for the dismiss button. Falls back to the drawer.close catalog key.string—
panelClassExtra class(es) on the box that slides in, not the CDK pane.WrClassInput—

Layout directives

NameDescriptionTypeDefault
[wrDrawerTitle]Styles the title row.directive—
[wrDrawerContent]Styles the scrollable body.directive—
[wrDrawerFooter]Styles the footer row.directive—
alignHorizontal alignment of the footer content.'start' | 'center' | 'end''end'
[wrDrawerClose]="value?"Closes the drawer on click. The value is the close result of a service-opened drawer.directive—

Available inside a service-opened drawer

NameDescriptionTypeDefault
WR_DRAWER_DATAThe data payload passed to open(). undefined when you didn't pass any.InjectionToken<D>—
WrDrawerRefThe open drawer’s own ref — call close(result) to dismiss it from the content.WrDrawerRef<unknown, unknown>—
WR_DRAWER_REFThe same ref, used by [wrDrawerClose]. Prefer inject(WrDrawerRef).InjectionToken<WrDrawerRef<C, R>>—

CSS variables

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

VariableDefaultDeclared on
--wr-drawer-bgvar(--wr-color-surface).wr-drawer__panel
--wr-drawer-close-font-sizevar(--wr-text-base).wr-drawer__close
--wr-drawer-close-paddingvar(--wr-space-xs).wr-drawer__close
--wr-drawer-close-radiusvar(--wr-border-radius-sm).wr-drawer__close
--wr-drawer-content-font-sizevar(--wr-text-base).wr-drawer__content
--wr-drawer-content-line-heightvar(--wr-text-base-leading).wr-drawer__content
--wr-drawer-content-padding-xvar(--wr-space-xl).wr-drawer__content
--wr-drawer-content-padding-yvar(--wr-space-lg).wr-drawer__content
--wr-drawer-font-familyvar(--wr-font-family-base).wr-drawer__panel
--wr-drawer-footer-bordervar(--wr-border-base).wr-drawer__footer
--wr-drawer-footer-gapvar(--wr-space-sm).wr-drawer__footer
--wr-drawer-footer-padding-xvar(--wr-space-xl).wr-drawer__footer
--wr-drawer-footer-padding-yvar(--wr-space-md).wr-drawer__footer
--wr-drawer-radiusvar(--wr-border-radius-lg).wr-drawer__panel
--wr-drawer-shadowvar(--wr-shadow-modal).wr-drawer__panel
--wr-drawer-title-bordervar(--wr-border-base).wr-drawer__title
--wr-drawer-title-font-sizevar(--wr-text-lg).wr-drawer__title
--wr-drawer-title-font-weightvar(--wr-font-weight-semibold).wr-drawer__title
--wr-drawer-title-line-heightvar(--wr-text-base-leading).wr-drawer__title
--wr-drawer-title-padding-xvar(--wr-space-xl).wr-drawer__title
--wr-drawer-title-padding-yvar(--wr-space-lg).wr-drawer__title

Dependencies

Angular CDKa11y, overlay, portal
Built onOverlay

See also