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 Readtime Scheme dropdown Scheme toggle Select Site status Skeleton Skip link Spinner Statistic Subnavigation Surface Switch Table Tabs Tag Tile Timestamp Tooltip Video embed How to useProject FeltColor PalettesCustomizingDevelopers All PatternsAccordionAlertCall to ActionCardFilterFormSide navigationLink with iconLogo wallSearch barSticky bannerSticky cardTabsTagTile AI design principlesLegal requirementsTransparency noticesIconographyColorChatbot avatarsAnimation All Personalization PatternsAnnouncement FundamentalsColorAccessibility toolsAssistive technologiesCI/CDContentContributorsDesignDevelopmentManual testingResourcesScreen readers Design/code status Release notes Get support

Drawer

OverviewStyleGuidelinesCodeAccessibilityDemos
DrawerCollapsibleContainedFixed PositionI10nOverlayPattern CollapsiblePattern ResponsiveResponsive CollapsibleResponsive FlexResponsive GridResponsive SwitchSession StorageDrawerCollapsibleContainedFixed PositionI10nOverlayPattern CollapsiblePattern ResponsiveResponsive CollapsibleResponsive FlexResponsive GridResponsive SwitchSession Storage

Drawer

Minimal drawer. Click the trigger to slide the panel open.

#main {
  padding: var(--rh-space-2xl, 32px);
}
import '@rhds/elements/rh-drawer/rh-drawer.js';
<meta itemprop="description" content="Minimal drawer. Click the trigger to slide the panel open.">
<div id="container">
  <rh-drawer id="drawer" trigger-id="trigger">
    <p>Panel content slotted here.</p>
  </rh-drawer>
  <div id="main">
    <button id="trigger" aria-controls="drawer" aria-expanded="false">Toggle Drawer</button>

    <p>Click the trigger to open the drawer panel.</p>
  </div>
</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 = () => (
  <meta itemprop="description" content="Minimal drawer. Click the trigger to slide the panel open." />
  <div id="container">
    <Drawer id="drawer" trigger-id="trigger">
      <p>Panel content slotted here.</p>
    </Drawer>
    <div id="main">
      <button id="trigger" aria-controls="drawer" aria-expanded="false">Toggle Drawer</button>
      <p>Click the trigger to open the drawer panel.</p>
    </div>
  </div>
);

Collapsible

Collapsible drawer without container context. Always inline push mode with no responsive behavior.

#container {
  display: grid;
  grid-template-areas: 'drawer main';
  grid-template-columns: auto 1fr;
  block-size: 100%;
}

rh-drawer {
  grid-area: drawer;
}

#main {
  grid-area: main;
  padding: var(--rh-space-2xl, 32px);
  overflow: auto;

  & rh-alert {
    margin-block-end: var(--rh-space-2xl, 32px);
  }
}
import '@rhds/elements/rh-drawer/rh-drawer.js';
import '@rhds/elements/rh-alert/rh-alert.js';
<meta itemprop="description" content="Collapsible drawer without container context. Always inline push mode with no responsive behavior.">
<div id="container">
  <rh-drawer id="drawer" collapsible="" open="">
    <p>Panel content slotted here.</p>
  </rh-drawer>
  <div id="main">
    <rh-alert state="warning">
      <h4 slot="header">Not recommended for responsive layouts</h4>
      <p>This usage always pushes content regardless of viewport width, which can compress page content on narrow screens.</p>
      <p>Prefer the <a href="../responsive-switch/">responsive switch</a> pattern, which switches to a toggle + overlay panel at narrow widths.</p>
    </rh-alert>
    <p>This drawer has no container context on its parent, so it stays in inline push mode at any width.</p>
  </div>
</div>
import { Alert } from "@rhds/elements/react/rh-alert/rh-alert.js";
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 = () => (
  <meta itemprop="description" content="Collapsible drawer without container context. Always inline push mode with no responsive behavior." />
  <div id="container">
    <Drawer id="drawer" collapsible open>
      <p>Panel content slotted here.</p>
    </Drawer>
    <div id="main">
      <Alert state="warning">
        <h4 slot="header">Not recommended for responsive layouts</h4>
        <p>This usage always pushes content regardless of viewport width, which can compress page content on narrow screens.</p>
        <p>
Prefer the
<a href="../responsive-switch/">responsive switch</a>
pattern, which switches to a toggle + overlay panel at narrow widths.
        </p>
      </Alert>
      <p>This drawer has no container context on its parent, so it stays in inline push mode at any width.</p>
    </div>
  </div>
);

Contained

Drawer inside a resizable container. Drag the resize handle to see the drawer respond to container width.

@import url("/assets/packages/@rhds/elements/elements/rh-drawer/rh-drawer-lightdom-shim.css");

#wrapper {
  padding: var(--rh-space-2xl, 32px);
}

#container {
  container: rh-drawer / inline-size;
  display: flex;
  inline-size: 100%;
  block-size: 400px;
  resize: horizontal;
  overflow: hidden;
  border: var(--rh-border-width-sm, 1px) solid var(--rh-color-border-subtle);
  border-radius: var(--rh-border-radius-default, 3px);
}

#container > :not(rh-drawer) {
  flex: 1;
}

#main {
  padding: var(--rh-space-2xl, 32px);
  overflow: auto;
}

@container rh-drawer (min-width: 992px) {
  #trigger {
    display: none;
  }
}
import '@rhds/elements/rh-drawer/rh-drawer.js';
<meta itemprop="description" content="Drawer inside a resizable container. Drag the resize handle to see the drawer respond to container width.">
<div id="wrapper">
  <p>Drag the bottom-right corner of the box below to resize it. The drawer responds to the container width — inline at wide widths, overlay at narrow.</p>
  <div id="container">
    <rh-drawer id="drawer" trigger-id="trigger" open="">
      <p>Panel content slotted here.</p>
    </rh-drawer>
    <div id="main">
      <button id="trigger" aria-controls="drawer" aria-expanded="true">Toggle Drawer</button>

      <p>At wide container widths the drawer is always visible. Resize the container below 992px to see it switch to overlay mode.</p>
    </div>
  </div>
</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 = () => (
  <meta itemprop="description" content="Drawer inside a resizable container. Drag the resize handle to see the drawer respond to container width." />
  <div id="wrapper">
    <p>Drag the bottom-right corner of the box below to resize it. The drawer responds to the container width — inline at wide widths, overlay at narrow.</p>
    <div id="container">
      <Drawer id="drawer" trigger-id="trigger" open>
        <p>Panel content slotted here.</p>
      </Drawer>
      <div id="main">
        <button id="trigger" aria-controls="drawer" aria-expanded="true">Toggle Drawer</button>
        <p>At wide container widths the drawer is always visible. Resize the container below 992px to see it switch to overlay mode.</p>
      </div>
    </div>
  </div>
);

Fixed Position

Overlay drawer with fixed positioning. The panel is fixed to the viewport rather than a parent container.

#container {
  position: relative;
  display: grid;
  block-size: 100%;
}

rh-drawer,
#main {
  grid-area: 1 / 1;
}

#main {
  padding: var(--rh-space-2xl, 32px);
}
import '@rhds/elements/rh-drawer/rh-drawer.js';

const drawer = document.getElementById('drawer');
const form = document.getElementById('knobs');

form.addEventListener('input', () => {
  const data = Object.fromEntries(new FormData(form));
  drawer.inline = data.inline;
});
<meta itemprop="description" content="Overlay drawer with fixed positioning. The panel is fixed to the viewport rather than a parent container.">
<div id="container">
  <rh-drawer id="drawer" trigger-id="trigger" position="viewport">
    <p>Panel content slotted here.</p>
  </rh-drawer>
  <div id="main">
    <button id="trigger" aria-controls="drawer" aria-expanded="false">Toggle Drawer</button>

    <p>Click the trigger to open the drawer. The panel will be fixed to the viewport edge regardless of scroll position or container context.</p>
    <form id="knobs">
      <fieldset>
        <legend>Inline</legend>
        <label><input type="radio" name="inline" value="start" checked=""> start</label>
        <label><input type="radio" name="inline" value="end"> end</label>
      </fieldset>
    </form>
  </div>
</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 = () => (
  <meta itemprop="description" content="Overlay drawer with fixed positioning. The panel is fixed to the viewport rather than a parent container." />
  <div id="container">
    <Drawer id="drawer" trigger-id="trigger" position="viewport">
      <p>Panel content slotted here.</p>
    </Drawer>
    <div id="main">
      <button id="trigger" aria-controls="drawer" aria-expanded="false">Toggle Drawer</button>
      <p>Click the trigger to open the drawer. The panel will be fixed to the viewport edge regardless of scroll position or container context.</p>
      <form id="knobs">
        <fieldset>
<legend>Inline</legend>
<label>
  <input type="radio" name="inline" value="start" checked />
  start
</label>
<label>
  <input type="radio" name="inline" value="end" />
  end
</label>
        </fieldset>
      </form>
    </div>
  </div>
);

I10n

Right-to-left (RTL) responsive collapsible drawer. Pushes content at wide widths, overlaps at narrow widths. Panel position and collapse toggle respect document direction.

@import url("/assets/packages/@rhds/elements/elements/rh-drawer/rh-drawer-lightdom-shim.css");
#container {
  container: rh-drawer / inline-size;
  display: grid;
  block-size: 100%;
}

#container.layout-inline {
  grid-template-areas: 'drawer main';
  grid-template-columns: auto 1fr;
}

#container.layout-inline.end {
  grid-template-areas: 'main drawer';
  grid-template-columns: 1fr auto;
}

#container.layout-inline rh-drawer {
  grid-area: drawer;
}

#container.layout-inline #main {
  grid-area: main;
}

#main {
  padding: var(--rh-space-2xl, 32px);
  overflow: auto;
}
import '@rhds/elements/rh-drawer/rh-drawer.js';

const form = document.getElementById('knobs');
const drawer = document.getElementById('drawer');
const container = document.getElementById('container');

form.addEventListener('input', () => {
  const data = Object.fromEntries(new FormData(form));
  drawer.inline = data.inline;
  container.classList.toggle('end', data.inline === 'end');
});
<meta itemprop="description" content="Right-to-left (RTL) responsive collapsible drawer. Pushes content at wide widths, overlaps at narrow widths. Panel position and collapse toggle respect document direction.">
<div id="container" class="layout-inline" dir="rtl" lang="he">
  <rh-drawer id="drawer" collapsible="" open="" collapse-label-open="כווץ חלונית" collapse-label-closed="הרחב חלונית">
    <h3>כותרת</h3>
    <p>זהו תוכן הגוף של חלונית המגירה. בשפות מימין לשמאל, החלונית מופיעה בצד הנכון בהתאם לכיוון הטקסט.</p>
  </rh-drawer>
  <div id="main">
    <h3>תוכן העמוד</h3>
    <p>השתמש בלחצן ההקטנה כדי לכווץ ולהרחיב את החלונית.</p>
    <form id="knobs" dir="ltr" lang="en">
      <fieldset>
        <legend>Inline</legend>
        <label><input type="radio" name="inline" value="start" checked=""> start</label>
        <label><input type="radio" name="inline" value="end"> end</label>
      </fieldset>
    </form>
  </div>
</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 = () => (
  <meta itemprop="description" content="Right-to-left (RTL) responsive collapsible drawer. Pushes content at wide widths, overlaps at narrow widths. Panel position and collapse toggle respect document direction." />
  <div id="container" className="layout-inline" dir="rtl" lang="he">
    <Drawer id="drawer" collapsible open collapse-label-open="כווץ חלונית" collapse-label-closed="הרחב חלונית">
      <h3>כותרת</h3>
      <p>זהו תוכן הגוף של חלונית המגירה. בשפות מימין לשמאל, החלונית מופיעה בצד הנכון בהתאם לכיוון הטקסט.</p>
    </Drawer>
    <div id="main">
      <h3>תוכן העמוד</h3>
      <p>השתמש בלחצן ההקטנה כדי לכווץ ולהרחיב את החלונית.</p>
      <form id="knobs" dir="ltr" lang="en">
        <fieldset>
<legend>Inline</legend>
<label>
  <input type="radio" name="inline" value="start" checked />
  start
</label>
<label>
  <input type="radio" name="inline" value="end" />
  end
</label>
        </fieldset>
      </form>
    </div>
  </div>
);

Overlay

Simple overlay drawer with no container context. Click the trigger to open the panel.

#container {
  position: relative;
  display: grid;
  block-size: 100%;
}

rh-drawer,
#main {
  grid-area: 1 / 1;
}

#main {
  padding: var(--rh-space-2xl, 32px);
}
import '@rhds/elements/rh-drawer/rh-drawer.js';

const drawer = document.getElementById('drawer');
const form = document.getElementById('knobs');

form.addEventListener('input', () => {
  const data = Object.fromEntries(new FormData(form));
  drawer.inline = data.inline;
});
<meta itemprop="description" content="Simple overlay drawer with no container context. Click the trigger to open the panel.">
<div id="container">
  <rh-drawer id="drawer" trigger-id="trigger" inline="end">
    <p>Panel content slotted here.</p>
  </rh-drawer>
  <div id="main">
    <button id="trigger" aria-controls="drawer" aria-expanded="false">Toggle Drawer</button>

    <p>Click the trigger to open the drawer panel.</p>
    <form id="knobs">
      <fieldset>
        <legend>Inline</legend>
        <label><input type="radio" name="inline" value="start"> start</label>
        <label><input type="radio" name="inline" value="end" checked=""> end</label>
      </fieldset>
    </form>
  </div>
</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 = () => (
  <meta itemprop="description" content="Simple overlay drawer with no container context. Click the trigger to open the panel." />
  <div id="container">
    <Drawer id="drawer" trigger-id="trigger" inline="end">
      <p>Panel content slotted here.</p>
    </Drawer>
    <div id="main">
      <button id="trigger" aria-controls="drawer" aria-expanded="false">Toggle Drawer</button>
      <p>Click the trigger to open the drawer panel.</p>
      <form id="knobs">
        <fieldset>
<legend>Inline</legend>
<label>
  <input type="radio" name="inline" value="start" />
  start
</label>
<label>
  <input type="radio" name="inline" value="end" checked />
  end
</label>
        </fieldset>
      </form>
    </div>
  </div>
);

Pattern Collapsible

Pattern-style responsive collapsible navigation with navigation-primary and navigation-vertical. At wide widths the drawer is collapsible and pushes content inline. At narrow widths it switches to an overlay triggered by a menu button.

@import url("/assets/packages/@rhds/elements/elements/rh-drawer/rh-drawer-lightdom-shim.css");
@import url('/assets/packages/@rhds/elements/elements/rh-navigation-primary/rh-navigation-primary-lightdom.css');
@import url('/assets/packages/@rhds/elements/elements/rh-navigation-vertical/rh-navigation-vertical-lightdom.css');

#page {
  container: page / inline-size;
  height: 100%;
}

#layout {
  position: relative;
  display: grid;
  height: 100%;
  grid-template-areas: 'nav' 'content';
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr;
  min-block-size: 400px;
}

rh-navigation-primary {
  grid-area: nav;
}

#drawer-context {
  container: rh-drawer / inline-size;
  grid-area: content;
  display: grid;
  grid-template-columns: 1fr;
  position: relative;
  height: 100%;

  @container page (min-width: 992px) {
    grid-column: 1 / -1;
    grid-template-columns: auto 1fr;
  }
}

rh-drawer,
#main {
  grid-area: 1 / 1;
}

@container page (min-width: 992px) {
  rh-drawer {
    grid-column: 1;
  }

  #main {
    grid-column: 2;
  }
}

#main {
  padding: var(--rh-space-2xl, 32px);
}

#logo-trigger {
  display: flex;
  align-items: center;
  column-gap: var(--rh-space-xl, 24px);
  block-size: 100%;

  & #nav-trigger {
    position: relative;
    display: flex;
    align-items: center;
    block-size: 100%;
    background-color: transparent;
    padding: var(--rh-space-md, 8px);
    border: none;
    border-radius: 5px;
    cursor: pointer;

    & rh-icon {
      --rh-icon-size: 18px;
      color: var(--rh-color-icon-secondary);
    }

    &::after {
      content: '';
      position: absolute;
      inset: 0;
      border-block-end: 4px solid transparent;
    }

    &:hover::after,
    &:focus-visible::after {
      border-block-end-color: var(--rh-color-border-subtle);
    }

    &:active::after {
      border-block-end-color: var(--rh-color-accent-brand);
    }

    &:focus-visible {
      outline: var(--rh-border-width-md, 2px) solid var(--rh-color-interactive-primary-default);
      outline-offset: 3px;
    }

    @container page (min-width: 992px) {
      display: none;
    }
  }

  & a {
    display: flex;
    align-items: center;
    block-size: 100%;
    line-height: 0;

    &:focus-visible {
      outline: var(--rh-border-width-md, 2px) solid var(--rh-color-interactive-primary-default);
      outline-offset: -2px;
      border-radius: var(--rh-border-radius-default, 3px);
    }
  }

  & svg {
    block-size: 24px;
    inline-size: 32px;
    overflow: visible;

    @container page (min-width: 1200px) {
      inline-size: 102px;
    }

    @container page (min-width: 1440px) {
      block-size: 30px;
      inline-size: 127px;
    }

    & #wordmark {
      display: none;

      @container page (min-width: 1200px) {
        display: revert;
      }
    }
  }
}

rh-navigation-vertical {
  overflow-y: auto;
  padding-block-start: var(--rh-space-3xl, 48px);
}

.visually-hidden {
  position: fixed;
  clip: rect(0, 0, 0, 0);
  overflow: hidden;
  white-space: nowrap;
  border: 0;
}
import '@rhds/elements/rh-drawer/rh-drawer.js';
import '@rhds/elements/rh-navigation-primary/rh-navigation-primary.js';
import '@rhds/elements/rh-navigation-vertical/rh-navigation-vertical.js';
import '@rhds/elements/rh-navigation-link/rh-navigation-link.js';

const drawer = document.getElementById('side-nav');

drawer.addEventListener('threshold', (e) => {
  if (e.direction === 'above') {
    drawer.collapsible = true;
    drawer.open = true;
  } else {
    drawer.collapsible = false;
    drawer.open = false;
  }
});
<meta itemprop="description" content="Pattern-style responsive collapsible navigation with navigation-primary and navigation-vertical. At wide widths the drawer is collapsible and pushes content inline. At narrow widths it switches to an overlay triggered by a menu button.">

<div id="page">
  <div id="layout">
    <rh-navigation-primary role="navigation">
      <div id="logo-trigger" slot="logo">
        <button id="nav-trigger" aria-label="Open navigation" aria-controls="side-nav" aria-expanded="false">
<rh-icon icon="menu-bars" set="ui"></rh-icon>
        </button>
        <a href="#">
<svg preserveAspectRatio="xMinYMid slice" viewBox="0 0 613 145" role="img" aria-labelledby="logo-title">
  <title id="logo-title">Red Hat</title>
  <g id="fedora">
    <path id="hat" fill="var(--rh-color-brand-red, #ee0000)" d="M127.47,83.49c12.51,0,30.61-2.58,30.61-17.46a14,14,0,0,0-.31-3.42l-7.45-32.36c-1.72-7.12-3.23-10.35-15.73-16.6C124.89,8.69,103.76.5,97.51.5,91.69.5,90,8,83.06,8c-6.68,0-11.64-5.6-17.89-5.6-6,0-9.91,4.09-12.93,12.5,0,0-8.41,23.72-9.49,27.16A6.43,6.43,0,0,0,42.53,44c0,9.22,36.3,39.45,84.94,39.45M160,72.07c1.73,8.19,1.73,9.05,1.73,10.13,0,14-15.74,21.77-36.43,21.77C78.54,104,37.58,76.6,37.58,58.49a18.45,18.45,0,0,1,1.51-7.33C22.27,52,.5,55,.5,74.22c0,31.48,74.59,70.28,133.65,70.28,45.28,0,56.7-20.48,56.7-36.65,0-12.72-11-27.16-30.83-35.78"></path>
    <path id="band" d="M160,72.07c1.73,8.19,1.73,9.05,1.73,10.13,0,14-15.74,21.77-36.43,21.77C78.54,104,37.58,76.6,37.58,58.49a18.45,18.45,0,0,1,1.51-7.33l3.66-9.06A6.43,6.43,0,0,0,42.53,44c0,9.22,36.3,39.45,84.94,39.45,12.51,0,30.61-2.58,30.61-17.46a14,14,0,0,0-.31-3.42Z"></path>
  </g>
  <path id="wordmark" fill="light-dark(var(--rh-color-text-primary-on-light, #151515), var(--rh-color-text-primary-on-dark, #ffffff))" d="M579.74,92.8c0,11.89,7.15,17.67,20.19,17.67a52.11,52.11,0,0,0,11.89-1.68V95a24.84,24.84,0,0,1-7.68,1.16c-5.37,0-7.36-1.68-7.36-6.73V68.3h15.56V54.1H596.78v-18l-17,3.68V54.1H568.49V68.3h11.25Zm-53,.32c0-3.68,3.69-5.47,9.26-5.47a43.12,43.12,0,0,1,10.1,1.26v7.15a21.51,21.51,0,0,1-10.63,2.63c-5.46,0-8.73-2.1-8.73-5.57m5.2,17.56c6,0,10.84-1.26,15.36-4.31v3.37h16.82V74.08c0-13.56-9.14-21-24.39-21-8.52,0-16.94,2-26,6.1l6.1,12.52c6.52-2.74,12-4.42,16.83-4.42,7,0,10.62,2.73,10.62,8.31v2.73a49.53,49.53,0,0,0-12.62-1.58c-14.31,0-22.93,6-22.93,16.73,0,9.78,7.78,17.24,20.19,17.24m-92.44-.94h18.09V80.92h30.29v28.82H506V36.12H487.93V64.41H457.64V36.12H439.55ZM370.62,81.87c0-8,6.31-14.1,14.62-14.1A17.22,17.22,0,0,1,397,72.09V91.54A16.36,16.36,0,0,1,385.24,96c-8.2,0-14.62-6.1-14.62-14.09m26.61,27.87h16.83V32.44l-17,3.68V57.05a28.3,28.3,0,0,0-14.2-3.68c-16.19,0-28.92,12.51-28.92,28.5a28.25,28.25,0,0,0,28.4,28.6,25.12,25.12,0,0,0,14.93-4.83ZM320,67c5.36,0,9.88,3.47,11.67,8.83H308.47C310.15,70.3,314.36,67,320,67M291.33,82c0,16.2,13.25,28.82,30.28,28.82,9.36,0,16.2-2.53,23.25-8.42l-11.26-10c-2.63,2.74-6.52,4.21-11.14,4.21a14.39,14.39,0,0,1-13.68-8.83h39.65V83.55c0-17.67-11.88-30.39-28.08-30.39a28.57,28.57,0,0,0-29,28.81M262,51.58c6,0,9.36,3.78,9.36,8.31S268,68.2,262,68.2H244.11V51.58Zm-36,58.16h18.09V82.92h13.77l13.89,26.82H292l-16.2-29.45a22.27,22.27,0,0,0,13.88-20.72c0-13.25-10.41-23.45-26-23.45H226Z"></path>
</svg>
        </a>
      </div>
    </rh-navigation-primary>

    <div id="drawer-context">
      <rh-drawer id="side-nav" trigger-id="nav-trigger" accessible-label="Navigation">
        <rh-navigation-vertical>
<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/spacing">Spacing</rh-navigation-link>
</rh-navigation-vertical-list>

<rh-navigation-link href="#release-notes">Release Notes</rh-navigation-link>
        </rh-navigation-vertical>
      </rh-drawer>

      <div id="main">
        <p>At wide container widths the drawer pushes content aside with a
collapse toggle. At narrow widths it switches to an overlay triggered
by the menu button.</p>
      </div>
    </div>
  </div>
</div>
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 { NavigationPrimary } from "@rhds/elements/react/rh-navigation-primary/rh-navigation-primary.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 = () => (
  <meta itemprop="description" content="Pattern-style responsive collapsible navigation with navigation-primary and navigation-vertical. At wide widths the drawer is collapsible and pushes content inline. At narrow widths it switches to an overlay triggered by a menu button." />
  <div id="page">
    <div id="layout">
      <NavigationPrimary role="navigation">
        <div id="logo-trigger" slot="logo">
<button id="nav-trigger" aria-label="Open navigation" aria-controls="side-nav" aria-expanded="false">
  <Icon icon="menu-bars" set="ui" />
</button>
<a href="#">
  <svg preserveAspectRatio="xMinYMid slice" viewBox="0 0 613 145" role="img" aria-labelledby="logo-title">
    <title id="logo-title">Red Hat</title>
    <g id="fedora">
      <path id="hat" fill="var(--rh-color-brand-red, #ee0000)" d="M127.47,83.49c12.51,0,30.61-2.58,30.61-17.46a14,14,0,0,0-.31-3.42l-7.45-32.36c-1.72-7.12-3.23-10.35-15.73-16.6C124.89,8.69,103.76.5,97.51.5,91.69.5,90,8,83.06,8c-6.68,0-11.64-5.6-17.89-5.6-6,0-9.91,4.09-12.93,12.5,0,0-8.41,23.72-9.49,27.16A6.43,6.43,0,0,0,42.53,44c0,9.22,36.3,39.45,84.94,39.45M160,72.07c1.73,8.19,1.73,9.05,1.73,10.13,0,14-15.74,21.77-36.43,21.77C78.54,104,37.58,76.6,37.58,58.49a18.45,18.45,0,0,1,1.51-7.33C22.27,52,.5,55,.5,74.22c0,31.48,74.59,70.28,133.65,70.28,45.28,0,56.7-20.48,56.7-36.65,0-12.72-11-27.16-30.83-35.78" />
      <path id="band" d="M160,72.07c1.73,8.19,1.73,9.05,1.73,10.13,0,14-15.74,21.77-36.43,21.77C78.54,104,37.58,76.6,37.58,58.49a18.45,18.45,0,0,1,1.51-7.33l3.66-9.06A6.43,6.43,0,0,0,42.53,44c0,9.22,36.3,39.45,84.94,39.45,12.51,0,30.61-2.58,30.61-17.46a14,14,0,0,0-.31-3.42Z" />
    </g>
    <path id="wordmark" fill="light-dark(var(--rh-color-text-primary-on-light, #151515), var(--rh-color-text-primary-on-dark, #ffffff))" d="M579.74,92.8c0,11.89,7.15,17.67,20.19,17.67a52.11,52.11,0,0,0,11.89-1.68V95a24.84,24.84,0,0,1-7.68,1.16c-5.37,0-7.36-1.68-7.36-6.73V68.3h15.56V54.1H596.78v-18l-17,3.68V54.1H568.49V68.3h11.25Zm-53,.32c0-3.68,3.69-5.47,9.26-5.47a43.12,43.12,0,0,1,10.1,1.26v7.15a21.51,21.51,0,0,1-10.63,2.63c-5.46,0-8.73-2.1-8.73-5.57m5.2,17.56c6,0,10.84-1.26,15.36-4.31v3.37h16.82V74.08c0-13.56-9.14-21-24.39-21-8.52,0-16.94,2-26,6.1l6.1,12.52c6.52-2.74,12-4.42,16.83-4.42,7,0,10.62,2.73,10.62,8.31v2.73a49.53,49.53,0,0,0-12.62-1.58c-14.31,0-22.93,6-22.93,16.73,0,9.78,7.78,17.24,20.19,17.24m-92.44-.94h18.09V80.92h30.29v28.82H506V36.12H487.93V64.41H457.64V36.12H439.55ZM370.62,81.87c0-8,6.31-14.1,14.62-14.1A17.22,17.22,0,0,1,397,72.09V91.54A16.36,16.36,0,0,1,385.24,96c-8.2,0-14.62-6.1-14.62-14.09m26.61,27.87h16.83V32.44l-17,3.68V57.05a28.3,28.3,0,0,0-14.2-3.68c-16.19,0-28.92,12.51-28.92,28.5a28.25,28.25,0,0,0,28.4,28.6,25.12,25.12,0,0,0,14.93-4.83ZM320,67c5.36,0,9.88,3.47,11.67,8.83H308.47C310.15,70.3,314.36,67,320,67M291.33,82c0,16.2,13.25,28.82,30.28,28.82,9.36,0,16.2-2.53,23.25-8.42l-11.26-10c-2.63,2.74-6.52,4.21-11.14,4.21a14.39,14.39,0,0,1-13.68-8.83h39.65V83.55c0-17.67-11.88-30.39-28.08-30.39a28.57,28.57,0,0,0-29,28.81M262,51.58c6,0,9.36,3.78,9.36,8.31S268,68.2,262,68.2H244.11V51.58Zm-36,58.16h18.09V82.92h13.77l13.89,26.82H292l-16.2-29.45a22.27,22.27,0,0,0,13.88-20.72c0-13.25-10.41-23.45-26-23.45H226Z" />
  </svg>
</a>
        </div>
      </NavigationPrimary>
      <div id="drawer-context">
        <Drawer id="side-nav" trigger-id="nav-trigger" accessible-label="Navigation">
<NavigationVertical>
  <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/spacing">Spacing</NavigationLink>
  </NavigationVerticalList>
  <NavigationLink href="#release-notes">Release Notes</NavigationLink>
</NavigationVertical>
        </Drawer>
        <div id="main">
<p>At wide container widths the drawer pushes content aside with a
  collapse toggle. At narrow widths it switches to an overlay triggered
  by the menu button.</p>
        </div>
      </div>
    </div>
  </div>
);

Pattern Responsive

Pattern-style responsive side navigation with navigation-primary and navigation-vertical. At wide widths the drawer is always visible; at narrow widths it switches to an overlay triggered by a menu button.

@import url("/assets/packages/@rhds/elements/elements/rh-drawer/rh-drawer-lightdom-shim.css");
@import url('/assets/packages/@rhds/elements/elements/rh-navigation-primary/rh-navigation-primary-lightdom.css');
@import url('/assets/packages/@rhds/elements/elements/rh-navigation-vertical/rh-navigation-vertical-lightdom.css');

#page {
  container: page / inline-size;
  height: 100%;
}

#layout {
  position: relative;
  display: grid;
  height: 100%;
  grid-template-areas: 'nav' 'content';
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr;
  min-block-size: 400px;

  @container page (min-width: 992px) {
    grid-template-areas: 'nav nav' 'content content';
    grid-template-columns: auto 1fr;
  }
}

#drawer-context {
  container: rh-drawer / inline-size;
  grid-area: content;
  display: grid;
  grid-template-columns: 1fr;
  position: relative;
  height: 100%;

  @container page (min-width: 992px) {
    grid-column: 1 / -1;
    grid-template-columns: auto 1fr;
  }
}

rh-drawer,
#main {
  grid-area: 1 / 1;
}

@container page (min-width: 992px) {
  rh-drawer {
    grid-column: 1;
  }

  #main {
    grid-column: 2;
  }
}

rh-navigation-primary {
  grid-area: nav;
}

#logo-trigger {
  display: flex;
  align-items: center;
  column-gap: var(--rh-space-xl, 24px);
  block-size: 100%;

  & #nav-trigger {
    position: relative;
    display: flex;
    align-items: center;
    block-size: 100%;
    background-color: transparent;
    padding: var(--rh-space-md, 8px);
    border: none;
    border-radius: 5px;
    cursor: pointer;

    & rh-icon {
      --rh-icon-size: 18px;
      color: var(--rh-color-icon-secondary);
    }

    &::after {
      content: '';
      position: absolute;
      inset: 0;
      border-block-end: 4px solid transparent;
    }

    &:hover::after,
    &:focus-visible::after {
      border-block-end-color: var(--rh-color-border-subtle);
    }

    &:active::after {
      border-block-end-color: var(--rh-color-accent-brand);
    }

    &:focus-visible {
      outline: var(--rh-border-width-md, 2px) solid var(--rh-color-interactive-primary-default);
      outline-offset: 3px;
    }

    @container page (min-width: 992px) {
      display: none;
    }
  }

  & a {
    display: flex;
    align-items: center;
    block-size: 100%;
    line-height: 0;

    &:is(:hover, :focus) svg #fedora {
      @media (prefers-reduced-motion: no-preference) {
        transform: rotate(15deg);
      }
    }

    &:focus-visible {
      outline: var(--rh-border-width-md, 2px) solid var(--rh-color-interactive-primary-default);
      outline-offset: -2px;
      border-radius: var(--rh-border-radius-default, 3px);
    }
  }

  & svg {
    block-size: 24px;
    inline-size: 32px;
    overflow: visible;

    @container page (min-width: 1200px) {
      inline-size: 102px;
    }

    @container page (min-width: 1440px) {
      block-size: 30px;
      inline-size: 127px;
    }

    & #fedora {
      transform-box: fill-box;
      transform-origin: center;
      transition: transform 0.3s ease-in-out;
    }

    & #wordmark {
      display: none;

      @container page (min-width: 1200px) {
        display: revert;
      }
    }
  }
}

#main {
  padding: var(--rh-space-2xl, 32px);
}

rh-navigation-vertical {
  overflow-y: auto;
  padding-block-start: var(--rh-space-3xl, 48px);
}
import '@rhds/elements/rh-drawer/rh-drawer.js';
import '@rhds/elements/rh-navigation-primary/rh-navigation-primary.js';
import '@rhds/elements/rh-navigation-vertical/rh-navigation-vertical.js';
import '@rhds/elements/rh-navigation-link/rh-navigation-link.js';
<meta itemprop="description" content="Pattern-style responsive side navigation with navigation-primary and navigation-vertical. At wide widths the drawer is always visible; at narrow widths it switches to an overlay triggered by a menu button.">

<div id="page">
  <div id="layout">
    <rh-navigation-primary role="navigation">
      <div id="logo-trigger" slot="logo">
        <button id="nav-trigger" aria-label="Open navigation" aria-controls="side-nav" aria-expanded="false">
<rh-icon icon="menu-bars" set="ui"></rh-icon>
        </button>
        <a href="#">
<svg preserveAspectRatio="xMinYMid slice" viewBox="0 0 613 145" role="img" aria-labelledby="logo-title">
  <title id="logo-title">Red Hat</title>
  <g id="fedora">
    <path id="hat" fill="var(--rh-color-brand-red, #ee0000)" d="M127.47,83.49c12.51,0,30.61-2.58,30.61-17.46a14,14,0,0,0-.31-3.42l-7.45-32.36c-1.72-7.12-3.23-10.35-15.73-16.6C124.89,8.69,103.76.5,97.51.5,91.69.5,90,8,83.06,8c-6.68,0-11.64-5.6-17.89-5.6-6,0-9.91,4.09-12.93,12.5,0,0-8.41,23.72-9.49,27.16A6.43,6.43,0,0,0,42.53,44c0,9.22,36.3,39.45,84.94,39.45M160,72.07c1.73,8.19,1.73,9.05,1.73,10.13,0,14-15.74,21.77-36.43,21.77C78.54,104,37.58,76.6,37.58,58.49a18.45,18.45,0,0,1,1.51-7.33C22.27,52,.5,55,.5,74.22c0,31.48,74.59,70.28,133.65,70.28,45.28,0,56.7-20.48,56.7-36.65,0-12.72-11-27.16-30.83-35.78"></path>
    <path id="band" d="M160,72.07c1.73,8.19,1.73,9.05,1.73,10.13,0,14-15.74,21.77-36.43,21.77C78.54,104,37.58,76.6,37.58,58.49a18.45,18.45,0,0,1,1.51-7.33l3.66-9.06A6.43,6.43,0,0,0,42.53,44c0,9.22,36.3,39.45,84.94,39.45,12.51,0,30.61-2.58,30.61-17.46a14,14,0,0,0-.31-3.42Z"></path>
  </g>
  <path id="wordmark" fill="light-dark(var(--rh-color-text-primary-on-light, #151515), var(--rh-color-text-primary-on-dark, #ffffff))" d="M579.74,92.8c0,11.89,7.15,17.67,20.19,17.67a52.11,52.11,0,0,0,11.89-1.68V95a24.84,24.84,0,0,1-7.68,1.16c-5.37,0-7.36-1.68-7.36-6.73V68.3h15.56V54.1H596.78v-18l-17,3.68V54.1H568.49V68.3h11.25Zm-53,.32c0-3.68,3.69-5.47,9.26-5.47a43.12,43.12,0,0,1,10.1,1.26v7.15a21.51,21.51,0,0,1-10.63,2.63c-5.46,0-8.73-2.1-8.73-5.57m5.2,17.56c6,0,10.84-1.26,15.36-4.31v3.37h16.82V74.08c0-13.56-9.14-21-24.39-21-8.52,0-16.94,2-26,6.1l6.1,12.52c6.52-2.74,12-4.42,16.83-4.42,7,0,10.62,2.73,10.62,8.31v2.73a49.53,49.53,0,0,0-12.62-1.58c-14.31,0-22.93,6-22.93,16.73,0,9.78,7.78,17.24,20.19,17.24m-92.44-.94h18.09V80.92h30.29v28.82H506V36.12H487.93V64.41H457.64V36.12H439.55ZM370.62,81.87c0-8,6.31-14.1,14.62-14.1A17.22,17.22,0,0,1,397,72.09V91.54A16.36,16.36,0,0,1,385.24,96c-8.2,0-14.62-6.1-14.62-14.09m26.61,27.87h16.83V32.44l-17,3.68V57.05a28.3,28.3,0,0,0-14.2-3.68c-16.19,0-28.92,12.51-28.92,28.5a28.25,28.25,0,0,0,28.4,28.6,25.12,25.12,0,0,0,14.93-4.83ZM320,67c5.36,0,9.88,3.47,11.67,8.83H308.47C310.15,70.3,314.36,67,320,67M291.33,82c0,16.2,13.25,28.82,30.28,28.82,9.36,0,16.2-2.53,23.25-8.42l-11.26-10c-2.63,2.74-6.52,4.21-11.14,4.21a14.39,14.39,0,0,1-13.68-8.83h39.65V83.55c0-17.67-11.88-30.39-28.08-30.39a28.57,28.57,0,0,0-29,28.81M262,51.58c6,0,9.36,3.78,9.36,8.31S268,68.2,262,68.2H244.11V51.58Zm-36,58.16h18.09V82.92h13.77l13.89,26.82H292l-16.2-29.45a22.27,22.27,0,0,0,13.88-20.72c0-13.25-10.41-23.45-26-23.45H226Z"></path>
</svg>
        </a>
      </div>
    </rh-navigation-primary>

    <div id="drawer-context">
      <rh-drawer id="side-nav" trigger-id="nav-trigger" accessible-label="Navigation">
        <rh-navigation-vertical>
<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/spacing">Spacing</rh-navigation-link>
</rh-navigation-vertical-list>

<rh-navigation-link href="#release-notes">Release Notes</rh-navigation-link>
        </rh-navigation-vertical>
      </rh-drawer>

      <div id="main">
        <p>At wide container widths the drawer is always visible alongside page
content. At narrow widths it switches to an overlay triggered by the
menu button.</p>
      </div>
    </div>
  </div>
</div>
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 { NavigationPrimary } from "@rhds/elements/react/rh-navigation-primary/rh-navigation-primary.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 = () => (
  <meta itemprop="description" content="Pattern-style responsive side navigation with navigation-primary and navigation-vertical. At wide widths the drawer is always visible; at narrow widths it switches to an overlay triggered by a menu button." />
  <div id="page">
    <div id="layout">
      <NavigationPrimary role="navigation">
        <div id="logo-trigger" slot="logo">
<button id="nav-trigger" aria-label="Open navigation" aria-controls="side-nav" aria-expanded="false">
  <Icon icon="menu-bars" set="ui" />
</button>
<a href="#">
  <svg preserveAspectRatio="xMinYMid slice" viewBox="0 0 613 145" role="img" aria-labelledby="logo-title">
    <title id="logo-title">Red Hat</title>
    <g id="fedora">
      <path id="hat" fill="var(--rh-color-brand-red, #ee0000)" d="M127.47,83.49c12.51,0,30.61-2.58,30.61-17.46a14,14,0,0,0-.31-3.42l-7.45-32.36c-1.72-7.12-3.23-10.35-15.73-16.6C124.89,8.69,103.76.5,97.51.5,91.69.5,90,8,83.06,8c-6.68,0-11.64-5.6-17.89-5.6-6,0-9.91,4.09-12.93,12.5,0,0-8.41,23.72-9.49,27.16A6.43,6.43,0,0,0,42.53,44c0,9.22,36.3,39.45,84.94,39.45M160,72.07c1.73,8.19,1.73,9.05,1.73,10.13,0,14-15.74,21.77-36.43,21.77C78.54,104,37.58,76.6,37.58,58.49a18.45,18.45,0,0,1,1.51-7.33C22.27,52,.5,55,.5,74.22c0,31.48,74.59,70.28,133.65,70.28,45.28,0,56.7-20.48,56.7-36.65,0-12.72-11-27.16-30.83-35.78" />
      <path id="band" d="M160,72.07c1.73,8.19,1.73,9.05,1.73,10.13,0,14-15.74,21.77-36.43,21.77C78.54,104,37.58,76.6,37.58,58.49a18.45,18.45,0,0,1,1.51-7.33l3.66-9.06A6.43,6.43,0,0,0,42.53,44c0,9.22,36.3,39.45,84.94,39.45,12.51,0,30.61-2.58,30.61-17.46a14,14,0,0,0-.31-3.42Z" />
    </g>
    <path id="wordmark" fill="light-dark(var(--rh-color-text-primary-on-light, #151515), var(--rh-color-text-primary-on-dark, #ffffff))" d="M579.74,92.8c0,11.89,7.15,17.67,20.19,17.67a52.11,52.11,0,0,0,11.89-1.68V95a24.84,24.84,0,0,1-7.68,1.16c-5.37,0-7.36-1.68-7.36-6.73V68.3h15.56V54.1H596.78v-18l-17,3.68V54.1H568.49V68.3h11.25Zm-53,.32c0-3.68,3.69-5.47,9.26-5.47a43.12,43.12,0,0,1,10.1,1.26v7.15a21.51,21.51,0,0,1-10.63,2.63c-5.46,0-8.73-2.1-8.73-5.57m5.2,17.56c6,0,10.84-1.26,15.36-4.31v3.37h16.82V74.08c0-13.56-9.14-21-24.39-21-8.52,0-16.94,2-26,6.1l6.1,12.52c6.52-2.74,12-4.42,16.83-4.42,7,0,10.62,2.73,10.62,8.31v2.73a49.53,49.53,0,0,0-12.62-1.58c-14.31,0-22.93,6-22.93,16.73,0,9.78,7.78,17.24,20.19,17.24m-92.44-.94h18.09V80.92h30.29v28.82H506V36.12H487.93V64.41H457.64V36.12H439.55ZM370.62,81.87c0-8,6.31-14.1,14.62-14.1A17.22,17.22,0,0,1,397,72.09V91.54A16.36,16.36,0,0,1,385.24,96c-8.2,0-14.62-6.1-14.62-14.09m26.61,27.87h16.83V32.44l-17,3.68V57.05a28.3,28.3,0,0,0-14.2-3.68c-16.19,0-28.92,12.51-28.92,28.5a28.25,28.25,0,0,0,28.4,28.6,25.12,25.12,0,0,0,14.93-4.83ZM320,67c5.36,0,9.88,3.47,11.67,8.83H308.47C310.15,70.3,314.36,67,320,67M291.33,82c0,16.2,13.25,28.82,30.28,28.82,9.36,0,16.2-2.53,23.25-8.42l-11.26-10c-2.63,2.74-6.52,4.21-11.14,4.21a14.39,14.39,0,0,1-13.68-8.83h39.65V83.55c0-17.67-11.88-30.39-28.08-30.39a28.57,28.57,0,0,0-29,28.81M262,51.58c6,0,9.36,3.78,9.36,8.31S268,68.2,262,68.2H244.11V51.58Zm-36,58.16h18.09V82.92h13.77l13.89,26.82H292l-16.2-29.45a22.27,22.27,0,0,0,13.88-20.72c0-13.25-10.41-23.45-26-23.45H226Z" />
  </svg>
</a>
        </div>
      </NavigationPrimary>
      <div id="drawer-context">
        <Drawer id="side-nav" trigger-id="nav-trigger" accessible-label="Navigation">
<NavigationVertical>
  <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/spacing">Spacing</NavigationLink>
  </NavigationVerticalList>
  <NavigationLink href="#release-notes">Release Notes</NavigationLink>
</NavigationVertical>
        </Drawer>
        <div id="main">
<p>At wide container widths the drawer is always visible alongside page
  content. At narrow widths it switches to an overlay triggered by the
  menu button.</p>
        </div>
      </div>
    </div>
  </div>
);

Responsive Collapsible

Responsive collapsible drawer. Pushes content at wide container widths, overlaps at narrow widths.

@import url("/assets/packages/@rhds/elements/elements/rh-drawer/rh-drawer-lightdom-shim.css");
#container {
  container: rh-drawer / inline-size;
  display: grid;
  block-size: 100%;
}

#container.layout-inline {
  grid-template-areas: 'drawer main';
  grid-template-columns: auto 1fr;
}

#container.layout-inline.end {
  grid-template-areas: 'main drawer';
  grid-template-columns: 1fr auto;
}

#container.layout-inline rh-drawer {
  grid-area: drawer;
}

#container.layout-inline #main {
  grid-area: main;
}

#main {
  padding: var(--rh-space-2xl, 32px);
  overflow: auto;

  & rh-alert {
    margin-block-end: var(--rh-space-2xl, 32px);
  }
}
import '@rhds/elements/rh-drawer/rh-drawer.js';
import '@rhds/elements/rh-alert/rh-alert.js';

const form = document.getElementById('knobs');
const drawer = document.getElementById('drawer');
const container = document.getElementById('container');

form.addEventListener('input', () => {
  const data = Object.fromEntries(new FormData(form));
  drawer.inline = data.inline;
  container.classList.toggle('end', data.inline === 'end');
});
<meta itemprop="description" content="Responsive collapsible drawer. Pushes content at wide container widths, overlaps at narrow widths.">
<div id="container" class="layout-inline">
  <rh-drawer id="drawer" collapsible="" open="">
    <p>Panel content slotted here.</p>
  </rh-drawer>
  <div id="main">
    <rh-alert state="warning">
      <h4 slot="header">Narrow viewport Warning</h4>
      <p>This usage always pushes content regardless of viewport width, which can compress page content on narrow screens. </p>
      <p>For most use cases, prefer the <a href="../responsive-switch/">responsive switch</a> pattern, which switches to a toggle + overlay panel at narrow widths.</p>
    </rh-alert>
    <p>At wide container widths the drawer pushes this content aside. At narrow widths it overlaps.</p>
    <form id="knobs">
      <fieldset>
        <legend>Inline</legend>
        <label><input type="radio" name="inline" value="start" checked=""> start</label>
        <label><input type="radio" name="inline" value="end"> end</label>
      </fieldset>
    </form>
  </div>
</div>
import { Alert } from "@rhds/elements/react/rh-alert/rh-alert.js";
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 = () => (
  <meta itemprop="description" content="Responsive collapsible drawer. Pushes content at wide container widths, overlaps at narrow widths." />
  <div id="container" className="layout-inline">
    <Drawer id="drawer" collapsible open>
      <p>Panel content slotted here.</p>
    </Drawer>
    <div id="main">
      <Alert state="warning">
        <h4 slot="header">Narrow viewport Warning</h4>
        <p>This usage always pushes content regardless of viewport width, which can compress page content on narrow screens.</p>
        <p>
For most use cases, prefer the
<a href="../responsive-switch/">responsive switch</a>
pattern, which switches to a toggle + overlay panel at narrow widths.
        </p>
      </Alert>
      <p>At wide container widths the drawer pushes this content aside. At narrow widths it overlaps.</p>
      <form id="knobs">
        <fieldset>
<legend>Inline</legend>
<label>
  <input type="radio" name="inline" value="start" checked />
  start
</label>
<label>
  <input type="radio" name="inline" value="end" />
  end
</label>
        </fieldset>
      </form>
    </div>
  </div>
);

Responsive Flex

Responsive drawer using a flex layout. Always visible at wide container widths, switches to overlay at narrow widths.

@import url("/assets/packages/@rhds/elements/elements/rh-drawer/rh-drawer-lightdom-shim.css");
#container {
  container: rh-drawer / inline-size;
  display: flex;
}

#container > :not(rh-drawer) {
  padding: var(--rh-space-2xl, 32px);
}

#container > div {
  flex: 1;
}

@container rh-drawer (min-width: 992px) {
  #trigger {
    display: none;
  }
}
import '@rhds/elements/rh-drawer/rh-drawer.js';
<meta itemprop="description" content="Responsive drawer using a flex layout. Always visible at wide container widths, switches to overlay at narrow widths.">
<div id="container">
  <rh-drawer id="drawer" trigger-id="trigger">
    Slotted Content
  </rh-drawer>

  <div>
    <button id="trigger" aria-controls="drawer" aria-expanded="false">Toggle Drawer</button>

    <p>This content is pushed aside by the drawer panel. When the panel collapses, this
      content expands to fill the full width.</p>
  </div>
</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 = () => (
  <meta itemprop="description" content="Responsive drawer using a flex layout. Always visible at wide container widths, switches to overlay at narrow widths." />
  <div id="container">
    <Drawer id="drawer" trigger-id="trigger">Slotted Content</Drawer>
    <div>
      <button id="trigger" aria-controls="drawer" aria-expanded="false">Toggle Drawer</button>
      <p>This content is pushed aside by the drawer panel. When the panel collapses, this
        content expands to fill the full width.</p>
    </div>
  </div>
);

Responsive Grid

Responsive drawer using a grid layout with container queries. Always visible at wide container widths, switches to overlay at narrow widths.

@import url("/assets/packages/@rhds/elements/elements/rh-drawer/rh-drawer-lightdom-shim.css");

#container {
  container: rh-drawer / inline-size;
  block-size: 100%;
}

#grid {
  display: grid;
  grid-template-columns: 1fr;
  block-size: 100%;
}

rh-drawer,
#main {
  grid-area: 1 / 1;
}

@container rh-drawer (min-width: 992px) {
  #grid {
    grid-template-columns: 320px 1fr;
  }

  rh-drawer {
    grid-column: 1;
  }

  #main {
    grid-column: 2;
  }

  #trigger {
    display: none;
  }
}

#main {
  padding: var(--rh-space-2xl, 32px);
  overflow: auto;
}
import '@rhds/elements/rh-drawer/rh-drawer.js';
<meta itemprop="description" content="Responsive drawer using a grid layout with container queries. Always visible at wide container widths, switches to overlay at narrow widths.">
<div id="container">
  <div id="grid">
    <rh-drawer id="drawer" trigger-id="trigger">
      <p>Panel content slotted here.</p>
    </rh-drawer>
    <div id="main">
      <button id="trigger" aria-controls="drawer" aria-expanded="false">Toggle Drawer</button>

      <p>At wide container widths the drawer is always visible. At narrow widths it becomes an overlay toggled by the button above.</p>
    </div>
  </div>
</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 = () => (
  <meta itemprop="description" content="Responsive drawer using a grid layout with container queries. Always visible at wide container widths, switches to overlay at narrow widths." />
  <div id="container">
    <div id="grid">
      <Drawer id="drawer" trigger-id="trigger">
        <p>Panel content slotted here.</p>
      </Drawer>
      <div id="main">
        <button id="trigger" aria-controls="drawer" aria-expanded="false">Toggle Drawer</button>
        <p>At wide container widths the drawer is always visible. At narrow widths it becomes an overlay toggled by the button above.</p>
      </div>
    </div>
  </div>
);

Responsive Switch

Mobile-first drawer that upgrades to collapsible at wide container widths. Uses trigger-id for overlay at narrow widths, adds collapsible at 992px.

@import url("/assets/packages/@rhds/elements/elements/rh-drawer/rh-drawer-lightdom-shim.css");
#container {
  container: rh-drawer / inline-size;
  display: grid;
  grid-template-areas: 'drawer main';
  grid-template-columns: auto 1fr;
  block-size: 100%;
}

rh-drawer {
  grid-area: drawer;
}

#main {
  grid-area: main;
  padding: var(--rh-space-2xl, 32px);
}

@container rh-drawer (min-width: 992px) {
  #trigger {
    display: none;
  }
}

.visually-hidden {
  position: fixed;
  clip: rect(0, 0, 0, 0);
  overflow: hidden;
  white-space: nowrap;
  border: 0;
}
import '@rhds/elements/rh-drawer/rh-drawer.js';

const drawer = document.getElementById('drawer');

drawer.addEventListener('threshold', (e) => {
  if (e.direction === 'above') {
    drawer.collapsible = true;
    drawer.open = true;
  } else {
    drawer.collapsible = false;
    drawer.open = false;
  }
});
<meta itemprop="description" content="Mobile-first drawer that upgrades to collapsible at wide container widths. Uses trigger-id for overlay at narrow widths, adds collapsible at 992px.">
<div id="container">
  <rh-drawer id="drawer" trigger-id="trigger">
    <h2 class="visually-hidden">Navigation</h2>
    <nav>
      <ul>
        <li><a href="#">Home</a></li>
        <li><a href="#">Get Started</a></li>
        <li><a href="#">Foundations</a></li>
        <li><a href="#">Release Notes</a></li>
      </ul>
    </nav>
  </rh-drawer>
  <div id="main">
    <button id="trigger" type="button">Open drawer</button>
    <p>At narrow widths, click the trigger to overlay the drawer. At 992px and above, the drawer becomes collapsible and pushes content inline.</p>
  </div>
</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 = () => (
  <meta itemprop="description" content="Mobile-first drawer that upgrades to collapsible at wide container widths. Uses trigger-id for overlay at narrow widths, adds collapsible at 992px." />
  <div id="container">
    <Drawer id="drawer" trigger-id="trigger">
      <h2 className="visually-hidden">Navigation</h2>
      <nav>
        <ul>
<li><a href="#">Home</a></li>
<li><a href="#">Get Started</a></li>
<li><a href="#">Foundations</a></li>
<li><a href="#">Release Notes</a></li>
        </ul>
      </nav>
    </Drawer>
    <div id="main">
      <button id="trigger" type="button">Open drawer</button>
      <p>At narrow widths, click the trigger to overlay the drawer. At 992px and above, the drawer becomes collapsible and pushes content inline.</p>
    </div>
  </div>
);

Session Storage

Persists drawer open/closed state using sessionStorage. Collapse the panel and refresh to see the state restored.

@import url("/assets/packages/@rhds/elements/elements/rh-drawer/rh-drawer-lightdom-shim.css");
#container {
  container: rh-drawer / inline-size;
  display: grid;
  block-size: 100%;
}

#container.layout-inline {
  grid-template-areas: 'drawer main';
  grid-template-columns: auto 1fr;
}

#container.layout-inline rh-drawer {
  grid-area: drawer;
}

#container.layout-inline #main {
  grid-area: main;
}

#main {
  padding: var(--rh-space-2xl, 32px);
  overflow: auto;
}
import '@rhds/elements/rh-drawer/rh-drawer.js';

const drawer = document.getElementById('drawer');
const STORAGE_KEY = 'demo-drawer:open';

await customElements.whenDefined('rh-drawer');
await drawer.updateComplete;

const stored = sessionStorage.getItem(STORAGE_KEY);
if (stored !== null) {
  drawer.open = stored === 'true';
}

drawer.addEventListener('open', () => sessionStorage.setItem(STORAGE_KEY, 'true'));
drawer.addEventListener('close', () => sessionStorage.setItem(STORAGE_KEY, 'false'));
<meta itemprop="description" content="Persists drawer open/closed state using sessionStorage. Collapse the panel and refresh to see the state restored.">
<div id="container" class="layout-inline">
  <rh-drawer id="drawer" collapsible="" open="">
    <p>Collapse this panel, then refresh the page.
      The open/closed state will be restored from session storage.</p>
  </rh-drawer>
  <div id="main">

    <p>Toggle the drawer, then refresh to verify state is restored.</p>
    <p><small>Open/closed state persists in sessionStorage under key
      <code>"demo-drawer:open"</code>.</small></p>
  </div>
</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 = () => (
  <meta itemprop="description" content="Persists drawer open/closed state using sessionStorage. Collapse the panel and refresh to see the state restored." />
  <div id="container" className="layout-inline">
    <Drawer id="drawer" collapsible open>
      <p>Collapse this panel, then refresh the page.
        The open/closed state will be restored from session storage.</p>
    </Drawer>
    <div id="main">
      <p>Toggle the drawer, then refresh to verify state is restored.</p>
      <p>
        <small>
Open/closed state persists in sessionStorage under key
<code>"demo-drawer:open"</code>
.
        </small>
      </p>
    </div>
  </div>
);
© 2026 Red Hat Deploys by Netlify