Drawer

Pro

Premium slide-over panel from any edge with 6 semantic variants, 6 sizes, overlay/push modes, built-in variant-styled footer actions, loading & non-modal states, and ESC/backdrop close.

</>sv-drawer drawerpaneloverlayvariantsheetpush
import { SvDrawerComponent } from '@styloviz-pro/overlays';

Demo & usage

Basic usage

Pro

Usage example — open the gallery for the full interactive demo.

Pro

An interactive demo for this component lives in the gallery.

Open live demo

More examples

Back closes it, by default

Pro

Guard an unsaved form

Pro

Danger variant with subtitle

Pro

Bottom sheet

Pro

Loading during async save

Pro

Non-modal side panel

Pro

Push content aside

Pro

Modal, but not dimmed

Pro

Built-in footer actions

Pro

Show / hide header & footer

Pro

When to use

  • Showing secondary content or forms alongside the page without full navigation.
  • Filters, details, settings, carts — anything that slides over and dismisses.
  • Semantic panels (danger, success, warning) — reach for the matching `variant`.
  • For a centered, attention-grabbing dialog use a Modal.

Async states

  • loading

Inputs

NameTypeDefaultDescription
openboolean (two-way)falseControls drawer visibility. Use [(open)] for two-way binding.
positionDrawerPosition'right'Edge from which the drawer slides in.
sizeDrawerSize'md'Width (left/right) or height (top/bottom).
variantDrawerVariant'default'Semantic/visual variant. Drives the header icon badge + accent bar.
titlestring''Optional title in the default header slot.
subtitlestring''Optional secondary line beneath the title (also used for aria-describedby).
ariaLabelstring'Drawer'Accessible label used when no `title` is provided.
showHeaderbooleantrueRender the built-in header bar.
showHeaderDividerbooleantrueBottom dividing line under the header.
showFooterbooleantrueRender the footer slot.
showIconbooleantrueShow the built-in variant icon badge in the header.
showClosebooleantrueShow the close (×) button.
showBackdropboolean | nullnullRender the dimming backdrop (set false for a non-modal drawer).
modalityDrawerModality | nullnullWhich contract this drawer follows.
backdropBlurbooleantrueApply a frosted-glass blur to the backdrop.
closeOnBackdropbooleantrueClose when the backdrop is clicked.
closeOnEscbooleantrueClose on the ESC key.
loadingbooleanfalseBusy state. While `true` the panel shows a spinner overlay and all close paths (button, backdrop, ESC) are blocked so a running task can finish.
pendingDismissalSvPendingDismissal'block'May the user dismiss the drawer while `loading` is true? THE MODAL TWIN ANSWERED THIS AND THE DRAWER DID NOT. asks every async surface for a documented answer to "can the user dismiss this while it is in flight?" — Modal got `pendingDismissal` and `loadingCancelled`; Drawer kept a silent, hard-wired refusal, so a drawer whose request hung could not be closed by anything short of a reload. `'block'` keeps exactly today's behaviour and is the default. `'abort'` lets every close path through and emits `loadingCancelled` first, so the host can abort the work it started — the drawer does not own the request.
closeOnNavigationbooleantrueDoes a navigation close this drawer? A ROUTE CHANGE LEAVES THE DRAWER OPEN, over a page it has nothing to do with — and a drawer is worse than a modal here, because it is often non-modal: the user can carry on with the new page while a panel from the old one sits beside it. Takes effect once the app calls `provideSvOverlayRouteClose()` from `@styloviz/layout`; without it nothing changes. 's stack half landed in T1 — this is its history half.
backClosesbooleantrueDoes the browser Back button close this drawer? ON BY DEFAULT HERE, UNLIKE THE MODAL — and the asymmetry is the point. A drawer is usually a DESTINATION: a details panel for the row you clicked, a filter sheet you opened on purpose. Back is the gesture for "undo that", and on a phone it is the system gesture. A modal is more often a transient question, where Back should belong to the page. The mechanism is a marked `history` entry, not a route: the URL is untouched.
dirtybooleanfalseIs there unsaved work in this drawer?, AND A DRAWER IS THE WORST PLACE FOR THIS. It is the kit's preferred home for a long edit form — that is what the size and the scroll are for — and it is dismissed by a click anywhere in the large dim area beside it. One missed click discards the lot. THE SAME PROMPT AS THE MODAL'S, FROM THE SAME PLACE (`createCloseGuard`). The audit's warning was explicit: a "you have unsaved changes" prompt implemented four times will behave four ways.
canClose((reason: DrawerCloseReason) => boolean | void) | nullnullThe host's own veto, consulted before anything else. Return `false` to refuse a close, or nothing to have no opinion. A veto is silent — it does NOT raise the unsaved-changes prompt, because the host refused for its own reasons and owns its own messaging.
unsavedTitlestring'Discard your changes?'Heading of the unsaved-changes prompt.
unsavedMessagestring'This panel has changes that have not b…Body of the unsaved-changes prompt.
discardLabelstring'Discard'Label of the button that closes and loses the changes.
keepEditingLabelstring'Keep editing'Label of the button that returns to the panel.
loadingTextstring'Loading…'Accessible label announced while `loading` is true.
initialFocusstring''CSS selector for the element to focus first when the drawer opens (e.g. `'#search'`). Falls back to the first focusable element.
modeDrawerMode'overlay'Content relationship. `overlay` (default) floats above the page; `push` squeezes `pushTarget` aside so the panel and content sit side by side.
pushTargetstring''CSS selector for the element pushed aside in `mode="push"` (e.g. `'#app-content-area'`). No-op when empty or in overlay mode.
primaryActionstring''Built-in primary footer action label. Empty hides the button.
secondaryActionstring''Built-in secondary footer action label. Empty hides the button.
customClassstring''Extra CSS classes merged onto the panel.

Outputs

EventPayload typeDescription
loadingCancelledvoidThe drawer was dismissed while `loading` — by a close path under `pendingDismissal="abort"`, or by a navigation (which `loading` cannot hold back). Emitted BEFORE `closed`, so the host can stop the work.
openedvoidEmits when the drawer opens by any means.
closedvoidEmits when the drawer closes by any means.
dismissedDrawerCloseReasonEmitted alongside `closed`, describing what triggered the close. A close caused by a navigation or a Back press reports `'programmatic'` here — the published union cannot grow — and its exact reason on `dismissedBy`.
dismissedBySvCloseReasonEmitted alongside `dismissed` with the exact reason, including `'route'` (the app navigated) and `'history'` (the Back button) from
discardedDrawerCloseReasonThe user discarded unsaved changes; the close follows.
closeCancelledDrawerCloseReasonThe user backed out of a close and returned to the panel.
primaryClickvoidEmitted when the built-in primary footer action is clicked.
secondaryClickvoidEmitted when the built-in secondary footer action is clicked.
openChangebooleanEmits the new `open` whenever it changes — the output half of `[(open)]`.

Customization & theming

  • Six semantic variants with tinted header/footer, icon badge + accent bar; toggle the badge via `showIcon`.
  • Open from any edge; six sizes (xs–full). Header and footer are independently optional via `[showHeader]`, `[showHeaderDivider]` and `[showFooter]` (all default `true`).
  • Built-in variant-styled `primaryAction`/`secondaryAction` footer buttons, or fully custom `[slot=footer]`.
  • Overlay or push mode (`mode="push"` + `pushTarget`), modal or non-modal (`showBackdrop`), backdrop blur, ESC/backdrop toggles, and a spinner `loading` state.
  • Body scroll-lock with scrollbar-width compensation; `customClass` on the panel.
  • Route and history integration: `[closeOnNavigation]` (default on) closes the panel when the app navigates — a drawer is often non-modal, so one left open beside a new page is worse than a stale modal — and `[backCloses]` is default ON here, unlike the Modal, because a drawer is usually a destination rather than a transient question. Wire `provideSvOverlayRouteClose()` for the route half.
  • A controlled-close policy: `[dirty]` raises the same unsaved-changes prompt the Modal uses — over the panel, focused on Keep editing, closing afterwards with the reason the user actually acted on — and `[canClose]` is your own silent veto. `(discarded)` / `(closeCancelled)` report which way it went. One primitive for both families (`createCloseGuard` in `@styloviz/core`), because a prompt written twice drifts.
  • Responsive: non-`full` sizes cap to `90vw` (left/right) or `90vh` (top/bottom) so the drawer never exceeds a phone viewport, leaving a sliver of backdrop to tap.

Best practices

  • Do: Set `[dirty]` whenever the drawer holds a form — which is most of the time, since its width and scroll are what make it the right home for a long edit. It is dismissed by a click anywhere in the large dim area beside it, so without the guard one missed click discards the lot.
  • Do: Choose an edge (`position`) that matches the content — right for details, bottom for mobile sheets.
  • Do: Match the `variant` to intent, and use `[loading]` to block dismissal during async work.
  • Do: Trap focus while open and restore it on close (handled for modal drawers).
  • Do: When hiding the header with `[showHeader]="false"`, keep another dismissal path — a footer action, ESC, or backdrop click — since that removes the built-in close (×) button.
  • Tip: Use `[showBackdrop]="false"` for persistent side panels that leave the page interactive.

Accessibility

  • `role="dialog"`, `aria-modal` (when backdrop shown), labelled via `aria-labelledby` (title) and `aria-describedby` (subtitle); `aria-busy` while loading.
  • `modality` decides the contract. A **modal** drawer traps Tab, marks the rest of the page `inert`, locks the page scroll, asserts `aria-modal="true"` and moves focus to `initialFocus` or the first focusable element. An **inline** one does none of those: the page beside it stays usable, so it does not take focus either unless you name an `initialFocus`. Focus is restored on close whichever it is — and if the trigger is gone by then, to the nearest thing that outlived it.
  • ⚠️ `showBackdrop` used to decide all of that, which is why it is no longer the input to reach for: five behaviours branched on a name that described the dim layer. It now controls only the dim layer, and defaults to following the modality — so `[showBackdrop]="false"` on its own still gives you an inline drawer exactly as before. `modality` and `mode` are different axes: `mode` is overlay-vs-push (layout), `modality` is modal-vs-inline (may the page still be used).
  • Hiding the header (`[showHeader]="false"`) removes the built-in close button — provide an alternative dismissal (footer action, ESC, or backdrop) so keyboard users can always exit.
  • `opened`/`closed` fire for every transition; `dismissed` reports the reason (backdrop / escape / close-button / programmatic).

Feedback on Drawer

Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.

Loads giscus.app and GitHub — nothing is fetched until you click.