Drawer Dialog

Slide-in panel using native <dialog> element. Side drawers for navigation, settings, or detail views.

Overview

A drawer is a dialog that slides in from the side of the screen. It uses the native <dialog> element for built-in accessibility (focus trapping, escape key, backdrop). CSS handles the slide animation with @starting-style for entry transitions.

Key features:

  • Native <dialog> with focus trapping and escape key
  • Slides from left (start) or right (end)
  • CSS animation with @starting-style
  • Full-height panel with scrollable body

Basic Drawer

A right-side drawer with header, scrollable body, and footer actions.

<button type="button" id="open-drawer">Open Drawer</button> <dialog id="drawer" class="drawer" data-position="end"> <layout-stack data-layout-gap="l"> <header> <h2>Drawer Title</h2> <button type="button" class="ghost" id="close-drawer" aria-label="Close"> &#x2715; </button> </header> <div class="drawer-body"> <p>Drawer content slides in from the side.</p> </div> <footer> <layout-cluster data-layout-justify="flex-end" data-layout-gap="m"> <button type="button" class="secondary" id="cancel-drawer">Cancel</button> <button type="button" class="primary">Save</button> </layout-cluster> </footer> </layout-stack> </dialog> <script> const drawer = document.getElementById('drawer'); document.getElementById('open-drawer').onclick = () => drawer.showModal(); document.getElementById('close-drawer').onclick = () => drawer.close(); document.getElementById('cancel-drawer').onclick = () => drawer.close(); </script>

Drawer Styles

Add these styles to your project. The data-position attribute controls which side the drawer slides from.

.drawer { padding: 0; border: none; max-width: min(90vw, 400px); max-height: 100vh; height: 100vh; margin: 0; border-radius: 0; box-shadow: var(--shadow-xl); } /* Slide from right (end) */ .drawer[data-position="end"] { margin-inline-start: auto; } /* Slide from left (start) */ .drawer[data-position="start"] { margin-inline-end: auto; } .drawer::backdrop { background: var(--color-overlay-strong); backdrop-filter: blur(4px); } .drawer > layout-stack { padding: var(--size-l); height: 100%; } .drawer header { display: flex; justify-content: space-between; align-items: center; } .drawer header h2 { margin: 0; font-size: var(--font-size-xl); } .drawer-body { flex: 1; overflow-y: auto; } /* Animation */ .drawer { transform: translateX(100%); transition: transform var(--duration-normal, 300ms) var(--ease-out, ease-out), display var(--duration-normal, 300ms) allow-discrete, overlay var(--duration-normal, 300ms) allow-discrete; } .drawer[data-position="start"] { transform: translateX(-100%); } .drawer[open] { transform: translateX(0); } @starting-style { .drawer[open] { transform: translateX(100%); } .drawer[open][data-position="start"] { transform: translateX(-100%); } } /* Reduced motion */ @media (prefers-reduced-motion: reduce) { .drawer { transition-duration: 0s; } }

Navigation Drawer

A left-side drawer for mobile navigation. Use data-position="start" for conventional left-side navigation.

<dialog id="nav-drawer" class="drawer" data-position="start"> <layout-stack data-layout-gap="l"> <header> <h2>Navigation</h2> <button type="button" class="ghost" aria-label="Close">&#x2715;</button> </header> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about/">About</a></li> <li><a href="/products/">Products</a></li> <li><a href="/contact/">Contact</a></li> </ul> </nav> </layout-stack> </dialog>

Usage Notes

  • Position: Use data-position="end" for settings/details, data-position="start" for navigation
  • Width: Adjust max-width in the CSS (default: min(90vw, 400px))
  • Scrolling: The .drawer-body scrolls independently with overflow-y: auto
  • Click outside: Add a click listener on the dialog for backdrop dismissal
  • Animation: Uses @starting-style for entry animation. Respects prefers-reduced-motion

Related

Dialog Element

Native dialog documentation

Modal Dialog

Centered modal patterns