Skip to main content Home About the Design SystemRoadmap OverviewDesignersDevelopers OverviewColorGridIconographyInteractionsSpacingTypography Overview Global colorBox shadowTypographyBorderOpacitySpaceLengthIconBreakpointsMedia queries All elements Accordion Alert Announcement Audio player Avatar Back to top Badge Blockquote Breadcrumb Button group Button Card Chip Code block Call to action Dialog Disclosure Drawer Footer Health index Icon Jump links Menu dropdown Navigation link Navigation (primary) Navigation (secondary) Navigation (vertical) Pagination PopoverPlanned Progress stepper Scheme toggle Site status Skeleton Skip link Spinner Statistic Subnavigation Surface Switch Table Tabs Tag Tile Timestamp Tooltip Video embed OverviewColor PalettesCustomizingDevelopers All PatternsAccordionCall to ActionCardFilterFormLink with iconLogo wallSearch barSticky bannerSticky cardTabsTagTile All Personalization PatternsAnnouncement FundamentalsAccessibility toolsAssistive technologiesCI/CDContentContributorsDesignDevelopmentManual testingResourcesScreen readers Design/code status Release notes Get support

Drawer

OverviewStyleGuidelinesCodeAccessibilityDemos
DrawerAutoClosedExternal TriggerFixedFlowI10nInlineOverlayStorage KeyDrawerAutoClosedExternal TriggerFixedFlowI10nInlineOverlayStorage Key

Drawer

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.

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.

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.

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.

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.

#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 (&gt;= 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.

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.

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.

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.

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>
);
© 2026 Red Hat Deploys by Netlify