Drawer
On this page
Drawer
Edit element properties
import '@rhds/elements/rh-drawer/rh-drawer.js';
<rh-drawer open="">
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<div>
<h3>Main Content</h3>
<p>The default auto variant displays as inline at wide container widths and switches to
overlay below 992px.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
</rh-drawer>
import { Drawer } from "@rhds/elements/react/rh-drawer/rh-drawer.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<Drawer open>
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<div>
<h3>Main Content</h3>
<p>The default auto variant displays as inline at wide container widths and switches to
overlay below 992px.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
</Drawer>
);
Auto
The auto variant is the default behavior of drawer. Behaves as an overlay below 992px container width and switches to inline layout at >= 992px, pushing content aside rather than overlaying it.
Edit element properties
import '@rhds/elements/rh-drawer/rh-drawer.js';
const form = document.getElementById('knobs');
const drawer = document.getElementById('drawer');
form.addEventListener('input', function sync() {
const data = Object.fromEntries(new FormData(form));
drawer.position = data.position;
drawer.panel = data.panel || undefined;
drawer.fullscreen = 'fullscreen' in data;
if (!drawer.open) {
drawer.open = true;
}
});
<rh-drawer id="drawer" open="">
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<div>
<h3>Main Content</h3>
<p>Resize the container below 992px to see the drawer switch from inline to overlay.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.</p>
<form id="knobs">
<fieldset>
<legend>Position</legend>
<label><input type="radio" name="position" value="inline-start" checked=""> inline-start</label>
<label><input type="radio" name="position" value="inline-end"> inline-end</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label><input type="radio" name="panel" value="collapsible" checked=""> collapsible (default)</label>
<label><input type="radio" name="panel" value="resizable"> resizable</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label><input type="checkbox" name="fullscreen"> fullscreen</label>
</fieldset>
</form>
</div>
</rh-drawer>
import { Drawer } from "@rhds/elements/react/rh-drawer/rh-drawer.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<Drawer id="drawer" open>
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<div>
<h3>Main Content</h3>
<p>Resize the container below 992px to see the drawer switch from inline to overlay.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.</p>
<form id="knobs">
<fieldset>
<legend>Position</legend>
<label>
<input type="radio" name="position" value="inline-start" checked />
inline-start
</label>
<label>
<input type="radio" name="position" value="inline-end" />
inline-end
</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label>
<input type="radio" name="panel" value="collapsible" checked />
collapsible (default)
</label>
<label>
<input type="radio" name="panel" value="resizable" />
resizable
</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label>
<input type="checkbox" name="fullscreen" />
fullscreen
</label>
</fieldset>
</form>
</div>
</Drawer>
);
Closed
The drawer renders in a closed state by default when the open attribute is not set. The collapse toggle allows users to expand the panel to reveal its content.
Edit element properties
import '@rhds/elements/rh-drawer/rh-drawer.js';
<rh-drawer variant="inline">
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<div>
<h3>Documentation</h3>
<p>This content is pushed aside by the inline drawer panel. When the panel collapses, this
content expands to fill the full width.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
</rh-drawer>
import { Drawer } from "@rhds/elements/react/rh-drawer/rh-drawer.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<Drawer variant="inline">
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<div>
<h3>Documentation</h3>
<p>This content is pushed aside by the inline drawer panel. When the panel collapses, this
content expands to fill the full width.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
</Drawer>
);
External Trigger
Demonstrates using trigger-id to toggle the drawer with an external button. When a trigger is set, the collapsible toggle is automatically suppressed. Use the knobs to switch between auto, overlay and inline variants, position, panel mode, and fullscreen.
Edit element properties
import '@rhds/elements/rh-drawer/rh-drawer.js';
const trigger = document.getElementById('trigger');
const form = document.getElementById('knobs');
const drawer = document.getElementById('drawer');
drawer.addEventListener('open', () => trigger.setAttribute('aria-expanded', 'true'));
drawer.addEventListener('close', () => trigger.setAttribute('aria-expanded', 'false'));
form.addEventListener('input', function sync() {
const data = Object.fromEntries(new FormData(form));
drawer.variant = data.variant;
drawer.position = data.position;
drawer.panel = data.panel || undefined;
drawer.fullscreen = 'fullscreen' in data;
});
<button id="trigger" aria-controls="drawer" aria-expanded="false">Open Drawer</button>
<rh-drawer id="drawer" variant="auto" trigger-id="trigger">
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<form id="knobs">
<fieldset>
<legend>Variant</legend>
<label><input type="radio" name="variant" value="auto" checked=""> auto</label>
<label><input type="radio" name="variant" value="overlay"> overlay</label>
<label><input type="radio" name="variant" value="inline"> inline</label>
</fieldset>
<fieldset>
<legend>Position</legend>
<label><input type="radio" name="position" value="inline-start" checked=""> inline-start</label>
<label><input type="radio" name="position" value="inline-end"> inline-end</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label><input type="radio" name="panel" value="none" checked=""> none</label>
<label><input type="radio" name="panel" value="resizable"> resizable</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label><input type="checkbox" name="fullscreen"> fullscreen</label>
</fieldset>
</form>
<div>
<h3>Page Content</h3>
<p>Use the external trigger button to open and close this drawer.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
</rh-drawer>
import { Drawer } from "@rhds/elements/react/rh-drawer/rh-drawer.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<button id="trigger" aria-controls="drawer" aria-expanded="false">Open Drawer</button>
<Drawer id="drawer" variant="auto" trigger-id="trigger">
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<form id="knobs">
<fieldset>
<legend>Variant</legend>
<label>
<input type="radio" name="variant" value="auto" checked />
auto
</label>
<label>
<input type="radio" name="variant" value="overlay" />
overlay
</label>
<label>
<input type="radio" name="variant" value="inline" />
inline
</label>
</fieldset>
<fieldset>
<legend>Position</legend>
<label>
<input type="radio" name="position" value="inline-start" checked />
inline-start
</label>
<label>
<input type="radio" name="position" value="inline-end" />
inline-end
</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label>
<input type="radio" name="panel" value="none" checked />
none
</label>
<label>
<input type="radio" name="panel" value="resizable" />
resizable
</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label>
<input type="checkbox" name="fullscreen" />
fullscreen
</label>
</fieldset>
</form>
<div>
<h3>Page Content</h3>
<p>Use the external trigger button to open and close this drawer.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
</Drawer>
);
Fixed
The panel is always position: fixed, overlaying the page at all viewport sizes. It takes up no space in the document flow and is toggled exclusively by an external trigger. Use the knobs to switch position, enable resizing, or toggle fullscreen.
Edit element properties
import '@rhds/elements/rh-drawer/rh-drawer.js';
const trigger = document.getElementById('fixed-trigger');
const form = document.getElementById('knobs');
const drawer = document.getElementById('drawer');
drawer.addEventListener('open', () => trigger.setAttribute('aria-expanded', 'true'));
drawer.addEventListener('close', () => trigger.setAttribute('aria-expanded', 'false'));
form.addEventListener('input', function sync() {
const data = Object.fromEntries(new FormData(form));
drawer.position = data.position;
drawer.panel = data.panel || undefined;
drawer.fullscreen = 'fullscreen' in data;
});
<button id="fixed-trigger" aria-controls="drawer" aria-expanded="false">Open Drawer</button>
<rh-drawer id="drawer" variant="fixed" trigger-id="fixed-trigger">
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
</rh-drawer>
<form id="knobs">
<fieldset>
<legend>Position</legend>
<label><input type="radio" name="position" value="inline-start" checked=""> inline-start</label>
<label><input type="radio" name="position" value="inline-end"> inline-end</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label><input type="radio" name="panel" value="none" checked=""> none</label>
<label><input type="radio" name="panel" value="resizable"> resizable</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label><input type="checkbox" name="fullscreen"> fullscreen</label>
</fieldset>
</form>
import { Drawer } from "@rhds/elements/react/rh-drawer/rh-drawer.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<button id="fixed-trigger" aria-controls="drawer" aria-expanded="false">Open Drawer</button>
<Drawer id="drawer" variant="fixed" trigger-id="fixed-trigger">
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
</Drawer>
<form id="knobs">
<fieldset>
<legend>Position</legend>
<label>
<input type="radio" name="position" value="inline-start" checked />
inline-start
</label>
<label>
<input type="radio" name="position" value="inline-end" />
inline-end
</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label>
<input type="radio" name="panel" value="none" checked />
none
</label>
<label>
<input type="radio" name="panel" value="resizable" />
resizable
</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label>
<input type="checkbox" name="fullscreen" />
fullscreen
</label>
</fieldset>
</form>
);
Flow
The panel participates in the parent layout at desktop sizes (>= 992px), sitting in a CSS grid as a navigation sidebar. Below 992px it becomes a fixed overlay toggled by an external trigger. This demo uses rh-navigation-vertical to show a realistic navigation use case.
Edit element properties
#container {
display: grid;
grid-template-areas: 'mock-nav' 'content';
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
block-size: 100%;
@media (min-width: 992px) {
grid-template-areas: 'mock-nav mock-nav' 'drawer content';
grid-template-columns: 320px 1fr;
grid-template-rows: auto 1fr;
}
}
#mock-nav {
grid-area: mock-nav;
color-scheme: only dark;
display: flex;
flex-direction: row;
gap: var(--rh-space-md, 8px);
align-items: center;
background-color: var(--rh-color-surface-darkest, #151515);
padding: var(--rh-space-lg, 16px) var(--rh-space-2xl, 32px);
& #flow-trigger {
display: flex;
flex-direction: row;
gap: var(--rh-space-md, 8px);
background-color: transparent;
padding: var(--rh-space-md, 8px);
border: none;
&:focus-visible {
border-radius: var(--rh-border-radius-default, 3px);
outline: var(--rh-border-width-md, 2px) solid var(--rh-color-interactive-primary-default);
outline-offset: 2px;
}
@media (min-width: 992px) {
display: none;
}
}
& #logo {
color: var(--rh-color-text-primary);
}
}
rh-drawer {
grid-area: drawer;
}
#main {
grid-area: content;
padding: var(--rh-space-2xl, 32px);
}
import '@rhds/elements/rh-drawer/rh-drawer.js';
import '@rhds/elements/rh-navigation-vertical/rh-navigation-vertical.js';
import '@rhds/elements/rh-navigation-link/rh-navigation-link.js';
const navToggle = document.querySelector('#flow-trigger');
const toggleIcon = navToggle.querySelector('rh-icon');
const drawer = document.querySelector('rh-drawer');
function toggleSetState(state) {
if (state === 'open') {
toggleIcon.icon = 'close';
}
if (state === 'closed') {
toggleIcon.icon = 'list';
}
}
if (drawer.open) {
toggleSetState('open');
}
drawer.addEventListener('open', function() {
navToggle.setAttribute('aria-expanded', 'true');
toggleSetState('open');
});
drawer.addEventListener('close', function() {
navToggle.setAttribute('aria-expanded', 'false');
toggleSetState('closed');
});
<div id="container">
<div id="mock-nav">
<button id="flow-trigger" aria-label="Open drawer" aria-controls="flow-drawer" aria-expanded="true">
<rh-icon icon="list" set="ui"></rh-icon>
</button>
<div id="logo">Logo</div>
</div>
<rh-drawer id="flow-drawer" variant="flow" trigger-id="flow-trigger" open="">
<rh-navigation-vertical slot="body">
<rh-navigation-link href="#" current-page="">Home</rh-navigation-link>
<rh-navigation-vertical-list summary="Get Started">
<rh-navigation-link href="#get-started">Overview</rh-navigation-link>
<rh-navigation-link href="#get-started/designers">Designers</rh-navigation-link>
<rh-navigation-link href="#get-started/developers">Developers</rh-navigation-link>
</rh-navigation-vertical-list>
<rh-navigation-vertical-list summary="Foundations">
<rh-navigation-link href="#foundations">Overview</rh-navigation-link>
<rh-navigation-link href="#foundations/color">Color</rh-navigation-link>
<rh-navigation-link href="#foundations/grid">Grid</rh-navigation-link>
<rh-navigation-link href="#foundations/spacing">Spacing</rh-navigation-link>
<rh-navigation-link href="#foundations/typography">Typography</rh-navigation-link>
</rh-navigation-vertical-list>
<rh-navigation-vertical-list summary="Elements">
<rh-navigation-link href="#elements/accordion">Accordion</rh-navigation-link>
<rh-navigation-link href="#elements/alert">Alert</rh-navigation-link>
<rh-navigation-link href="#elements/button">Button</rh-navigation-link>
<rh-navigation-link href="#elements/card">Card</rh-navigation-link>
<rh-navigation-link href="#elements/dialog">Dialog</rh-navigation-link>
<rh-navigation-link href="#elements/drawer">Drawer</rh-navigation-link>
<rh-navigation-link href="#elements/tabs">Tabs</rh-navigation-link>
</rh-navigation-vertical-list>
<rh-navigation-link href="#release-notes">Release Notes</rh-navigation-link>
<rh-navigation-link href="#get-support">Get Support</rh-navigation-link>
</rh-navigation-vertical>
</rh-drawer>
<div id="main">
<h3>Page Content</h3>
<p>At desktop sizes (>= 992px) the navigation sidebar participates in the grid layout. Below
992px it becomes a fixed overlay toggled by the list icon button that will appear above.</p>
</div>
</div>
<link rel="stylesheet" href="../../rh-navigation-vertical/rh-navigation-vertical-lightdom.css">
import { Drawer } from "@rhds/elements/react/rh-drawer/rh-drawer.js";
import { Icon } from "@rhds/elements/react/rh-icon/rh-icon.js";
import { NavigationLink } from "@rhds/elements/react/rh-navigation-link/rh-navigation-link.js";
import { NavigationVertical } from "@rhds/elements/react/rh-navigation-vertical/rh-navigation-vertical.js";
import { NavigationVerticalList } from "@rhds/elements/react/rh-navigation-vertical-list/rh-navigation-vertical-list.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<div id="container">
<div id="mock-nav">
<button id="flow-trigger" aria-label="Open drawer" aria-controls="flow-drawer" aria-expanded="true">
<Icon icon="list" set="ui" />
</button>
<div id="logo">Logo</div>
</div>
<Drawer id="flow-drawer" variant="flow" trigger-id="flow-trigger" open>
<NavigationVertical slot="body">
<NavigationLink href="#" current-page>Home</NavigationLink>
<NavigationVerticalList summary="Get Started">
<NavigationLink href="#get-started">Overview</NavigationLink>
<NavigationLink href="#get-started/designers">Designers</NavigationLink>
<NavigationLink href="#get-started/developers">Developers</NavigationLink>
</NavigationVerticalList>
<NavigationVerticalList summary="Foundations">
<NavigationLink href="#foundations">Overview</NavigationLink>
<NavigationLink href="#foundations/color">Color</NavigationLink>
<NavigationLink href="#foundations/grid">Grid</NavigationLink>
<NavigationLink href="#foundations/spacing">Spacing</NavigationLink>
<NavigationLink href="#foundations/typography">Typography</NavigationLink>
</NavigationVerticalList>
<NavigationVerticalList summary="Elements">
<NavigationLink href="#elements/accordion">Accordion</NavigationLink>
<NavigationLink href="#elements/alert">Alert</NavigationLink>
<NavigationLink href="#elements/button">Button</NavigationLink>
<NavigationLink href="#elements/card">Card</NavigationLink>
<NavigationLink href="#elements/dialog">Dialog</NavigationLink>
<NavigationLink href="#elements/drawer">Drawer</NavigationLink>
<NavigationLink href="#elements/tabs">Tabs</NavigationLink>
</NavigationVerticalList>
<NavigationLink href="#release-notes">Release Notes</NavigationLink>
<NavigationLink href="#get-support">Get Support</NavigationLink>
</NavigationVertical>
</Drawer>
<div id="main">
<h3>Page Content</h3>
<p>At desktop sizes (>= 992px) the navigation sidebar participates in the grid layout. Below
992px it becomes a fixed overlay toggled by the list icon button that will appear above.</p>
</div>
</div>
<link rel="stylesheet" href="../../rh-navigation-vertical/rh-navigation-vertical-lightdom.css" />
);
I10n
Demonstrates right-to-left (RTL) language support. The panel position, resize handle, and keyboard interactions all respect the document direction.
Edit element properties
div[dir] {
block-size: 100%;
}
import '@rhds/elements/rh-drawer/rh-drawer.js';
const form = document.getElementById('knobs');
const drawer = document.getElementById('drawer');
form.addEventListener('input', function sync() {
const data = Object.fromEntries(new FormData(form));
drawer.position = data.position;
drawer.panel = data.panel || undefined;
drawer.fullscreen = 'fullscreen' in data;
if (!drawer.open) {
drawer.open = true;
}
});
<div dir="rtl" lang="he">
<rh-drawer id="drawer" variant="inline" open="">
<div slot="header">כותרת</div>
<div slot="body">
<p>זהו תוכן הגוף של חלונית המגירה. בשפות מימין לשמאל, החלונית מופיעה בצד הנכון בהתאם לכיוון הטקסט.</p>
</div>
<div slot="footer">כותרת תחתונה</div>
<div>
<h3>תוכן ראשי</h3>
<p>תוכן זה מכוסה על ידי חלונית המגירה. המגירה תומכת בשפות מימין לשמאל כולל עברית וערבית.</p>
<p>לורם איפסום דולור סיט אמט, קונסקטטור אדיפיסינג אלית. סת אלמנקום נימוצינור,, ושמן מעמידושמע
ותלמשנותי,, תוצנ, ,, פול, מוסן מנת, נמשח, , תוצנ מוסן מנת, , , דוסי מקנה גלisce לאsjust
ליבום סולמיה דימון, , ברומיה, , , .</p>
<form id="knobs" dir="ltr">
<fieldset>
<legend>Position</legend>
<label><input type="radio" name="position" value="inline-start" checked=""> inline-start</label>
<label><input type="radio" name="position" value="inline-end"> inline-end</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label><input type="radio" name="panel" value="collapsible" checked=""> collapsible (default)</label>
<label><input type="radio" name="panel" value="resizable"> resizable</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label><input type="checkbox" name="fullscreen"> fullscreen</label>
</fieldset>
</form>
</div>
</rh-drawer>
</div>
import { Drawer } from "@rhds/elements/react/rh-drawer/rh-drawer.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<div dir="rtl" lang="he">
<Drawer id="drawer" variant="inline" open>
<div slot="header">כותרת</div>
<div slot="body">
<p>זהו תוכן הגוף של חלונית המגירה. בשפות מימין לשמאל, החלונית מופיעה בצד הנכון בהתאם לכיוון הטקסט.</p>
</div>
<div slot="footer">כותרת תחתונה</div>
<div>
<h3>תוכן ראשי</h3>
<p>תוכן זה מכוסה על ידי חלונית המגירה. המגירה תומכת בשפות מימין לשמאל כולל עברית וערבית.</p>
<p>לורם איפסום דולור סיט אמט, קונסקטטור אדיפיסינג אלית. סת אלמנקום נימוצינור,, ושמן מעמידושמע
ותלמשנותי,, תוצנ, ,, פול, מוסן מנת, נמשח, , תוצנ מוסן מנת, , , דוסי מקנה גלisce לאsjust
ליבום סולמיה דימון, , ברומיה, , , .</p>
<form id="knobs" dir="ltr">
<fieldset>
<legend>Position</legend>
<label>
<input type="radio" name="position" value="inline-start" checked />
inline-start
</label>
<label>
<input type="radio" name="position" value="inline-end" />
inline-end
</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label>
<input type="radio" name="panel" value="collapsible" checked />
collapsible (default)
</label>
<label>
<input type="radio" name="panel" value="resizable" />
resizable
</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label>
<input type="checkbox" name="fullscreen" />
fullscreen
</label>
</fieldset>
</form>
</div>
</Drawer>
</div>
);
Inline
The panel pushes and pulls content in the normal flex flow. Below 992px, non-collapsible panels go off-screen and content takes full width. Includes demos for inline-start, inline-end, and resizable with an external trigger.
Edit element properties
import '@rhds/elements/rh-drawer/rh-drawer.js';
const form = document.getElementById('knobs');
const drawer = document.getElementById('drawer');
form.addEventListener('input', function sync() {
const data = Object.fromEntries(new FormData(form));
drawer.position = data.position;
drawer.panel = data.panel || undefined;
drawer.fullscreen = 'fullscreen' in data;
if (!drawer.open) {
drawer.open = true;
}
});
<rh-drawer id="drawer" variant="inline" open="">
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<div>
<h3>Page Content</h3>
<p>This content is pushed aside by the inline drawer panel. When the panel collapses,
this content expands to fill the full width.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.</p>
<form id="knobs">
<fieldset>
<legend>Position</legend>
<label><input type="radio" name="position" value="inline-start" checked=""> inline-start</label>
<label><input type="radio" name="position" value="inline-end"> inline-end</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label><input type="radio" name="panel" value="collapsible" checked=""> collapsible (default)</label>
<label><input type="radio" name="panel" value="resizable"> resizable</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label><input type="checkbox" name="fullscreen"> fullscreen</label>
</fieldset>
</form>
</div>
</rh-drawer>
import { Drawer } from "@rhds/elements/react/rh-drawer/rh-drawer.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<Drawer id="drawer" variant="inline" open>
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<div>
<h3>Page Content</h3>
<p>This content is pushed aside by the inline drawer panel. When the panel collapses,
this content expands to fill the full width.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.</p>
<form id="knobs">
<fieldset>
<legend>Position</legend>
<label>
<input type="radio" name="position" value="inline-start" checked />
inline-start
</label>
<label>
<input type="radio" name="position" value="inline-end" />
inline-end
</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label>
<input type="radio" name="panel" value="collapsible" checked />
collapsible (default)
</label>
<label>
<input type="radio" name="panel" value="resizable" />
resizable
</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label>
<input type="checkbox" name="fullscreen" />
fullscreen
</label>
</fieldset>
</form>
</div>
</Drawer>
);
Overlay
The panel overlays the content slot without shifting layout. Defaults to collapsible with an optional fullscreen toggle. Includes demos for inline-start, inline-end, and resizable with an external trigger.
Edit element properties
import '@rhds/elements/rh-drawer/rh-drawer.js';
const form = document.getElementById('knobs');
const drawer = document.getElementById('drawer');
form.addEventListener('input', function sync() {
const data = Object.fromEntries(new FormData(form));
drawer.position = data.position;
drawer.panel = data.panel || undefined;
drawer.fullscreen = 'fullscreen' in data;
if (!drawer.open) {
drawer.open = true;
}
});
<rh-drawer id="drawer" variant="overlay" open="">
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<div>
<h3>Main Content</h3>
<p>This content area is overlaid by the drawer panel.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.</p>
<form id="knobs">
<fieldset>
<legend>Position</legend>
<label><input type="radio" name="position" value="inline-start" checked=""> inline-start</label>
<label><input type="radio" name="position" value="inline-end"> inline-end</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label><input type="radio" name="panel" value="collapsible" checked=""> collapsible (default)</label>
<label><input type="radio" name="panel" value="resizable"> resizable</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label><input type="checkbox" name="fullscreen"> fullscreen</label>
</fieldset>
</form>
</div>
</rh-drawer>
import { Drawer } from "@rhds/elements/react/rh-drawer/rh-drawer.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<Drawer id="drawer" variant="overlay" open>
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<div>
<h3>Main Content</h3>
<p>This content area is overlaid by the drawer panel.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.</p>
<form id="knobs">
<fieldset>
<legend>Position</legend>
<label>
<input type="radio" name="position" value="inline-start" checked />
inline-start
</label>
<label>
<input type="radio" name="position" value="inline-end" />
inline-end
</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label>
<input type="radio" name="panel" value="collapsible" checked />
collapsible (default)
</label>
<label>
<input type="radio" name="panel" value="resizable" />
resizable
</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label>
<input type="checkbox" name="fullscreen" />
fullscreen
</label>
</fieldset>
</form>
</div>
</Drawer>
);
Storage Key
Demonstrates session persistence using the storage-key attribute. Each drawer saves its open/closed state and panel width to sessionStorage. Toggle the drawer or resize the panel, then navigate away and come back — the last state will be restored. Closing the tab clears the state.
Edit element properties
import '@rhds/elements/rh-drawer/rh-drawer.js';
const KNOBS_KEY = 'demo-storage-knobs';
const trigger = document.getElementById('trigger');
const form = document.getElementById('knobs');
const drawer = document.getElementById('drawer');
drawer.addEventListener('open', () => trigger.setAttribute('aria-expanded', 'true'));
drawer.addEventListener('close', () => trigger.setAttribute('aria-expanded', 'false'));
function saveKnobs() {
const data = Object.fromEntries(new FormData(form));
sessionStorage.setItem(KNOBS_KEY, JSON.stringify(data));
}
function restoreKnobs() {
try {
const raw = sessionStorage.getItem(KNOBS_KEY);
if (!raw) return;
const data = JSON.parse(raw);
for (const [name, value] of Object.entries(data)) {
const input = form.querySelector(`[name="${name}"][value="${value}"]`);
if (input) {
input.checked = true;
}
}
if (data.fullscreen) {
form.querySelector('[name="fullscreen"]').checked = true;
}
applyKnobs();
} catch {
// ignore corrupt data
}
}
function applyKnobs() {
const data = Object.fromEntries(new FormData(form));
const isResizable = data.panel === 'resizable';
drawer.variant = data.variant;
drawer.position = data.position;
drawer.panel = data.panel || undefined;
drawer.fullscreen = 'fullscreen' in data;
drawer.triggerId = isResizable ? 'trigger' : undefined;
trigger.disabled = !isResizable;
}
form.addEventListener('input', function() {
applyKnobs();
saveKnobs();
});
await customElements.whenDefined('rh-drawer');
await drawer.updateComplete;
restoreKnobs();
<rh-drawer id="drawer" variant="overlay" storage-key="demo-storage">
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<div>
<h3>Content</h3>
<p>Collapse or resize the drawer, navigate away, then return. The drawer state will
be restored from session storage.</p>
<form id="knobs">
<fieldset>
<legend>Variant</legend>
<label><input type="radio" name="variant" value="overlay" checked=""> overlay</label>
<label><input type="radio" name="variant" value="inline"> inline</label>
</fieldset>
<fieldset>
<legend>Position</legend>
<label><input type="radio" name="position" value="inline-start" checked=""> inline-start</label>
<label><input type="radio" name="position" value="inline-end"> inline-end</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label><input type="radio" name="panel" value="collapsible" checked=""> collapsible</label>
<label><input type="radio" name="panel" value="resizable"> resizable</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label><input type="checkbox" name="fullscreen"> fullscreen</label>
</fieldset>
</form>
<button id="trigger" disabled="" aria-controls="drawer" aria-expanded="true">Open Drawer</button>
<div class="help-text">Enabled when resizable is checked</div>
</div>
</rh-drawer>
import { Drawer } from "@rhds/elements/react/rh-drawer/rh-drawer.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<Drawer id="drawer" variant="overlay" storage-key="demo-storage">
<div slot="header">Header</div>
<div slot="body">Body</div>
<div slot="footer">Footer</div>
<div>
<h3>Content</h3>
<p>Collapse or resize the drawer, navigate away, then return. The drawer state will
be restored from session storage.</p>
<form id="knobs">
<fieldset>
<legend>Variant</legend>
<label>
<input type="radio" name="variant" value="overlay" checked />
overlay
</label>
<label>
<input type="radio" name="variant" value="inline" />
inline
</label>
</fieldset>
<fieldset>
<legend>Position</legend>
<label>
<input type="radio" name="position" value="inline-start" checked />
inline-start
</label>
<label>
<input type="radio" name="position" value="inline-end" />
inline-end
</label>
</fieldset>
<fieldset>
<legend>Panel</legend>
<label>
<input type="radio" name="panel" value="collapsible" checked />
collapsible
</label>
<label>
<input type="radio" name="panel" value="resizable" />
resizable
</label>
</fieldset>
<fieldset>
<legend>Fullscreen</legend>
<label>
<input type="checkbox" name="fullscreen" />
fullscreen
</label>
</fieldset>
</form>
<button id="trigger" disabled aria-controls="drawer" aria-expanded="true">Open Drawer</button>
<div className="help-text">Enabled when resizable is checked</div>
</div>
</Drawer>
);
Other libraries
To learn more about our other libraries, visit this page.
Feedback
To give feedback about anything on this page, contact us.