Side Panel

SidePanel is a portal-based drawer that slides in from the left or right edge of the screen. Use it for detail views, advanced filters, forms, or any secondary content that shouldn't fully interrupt page flow the way a Modal would.

  • Renders into a portal with role="dialog" / aria-modal="true", with aria-labelledby linked to the title.
  • Focus is trapped inside the panel while open and released on close.
  • Body scroll is locked while the panel is open.
  • Closes on the close button, Escape, or backdrop click — all call onClose.
  • Optional title, description, and footer slots, plus per-section className overrides for theming.

Import

import { SidePanel } from "h2o-library";

Usage

Basic

const [isOpen, setIsOpen] = useState(false);

<Button label="Open Panel" onClick={() => setIsOpen(true)} />

<SidePanel
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  title="Patient Details"
  description="View and edit patient information"
>
  <div style={{ padding: "0 1.5rem" }}>
    <p>Panel body content goes here.</p>
  </div>
</SidePanel>

The footer slot pins action buttons at the bottom regardless of scroll position.

<SidePanel
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  title="Edit Record"
  footer={
    <>
      <Button variant="ghost" label="Cancel" onClick={() => setIsOpen(false)} />
      <Button label="Save changes" onClick={handleSave} />
    </>
  }
>
  {/* form fields */}
</SidePanel>;

Left Position

Use position="left" for navigation-style or filter panels that pair with a right-aligned content area.

<SidePanel
  position="left"
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  title="Filters"
>
  {/* filter controls */}
</SidePanel>;

Sizes

<SidePanel size="sm" ...>  {/* 20rem — narrow auxiliary panels */}
<SidePanel size="md" ...>  {/* 24rem — default */}
<SidePanel size="lg" ...>  {/* 36rem — wide detail / form panels */}

Playground

Loading playground…

Keyboard

KeyAction
EscapeCalls onClose.
Tab / Shift+TabFocus is trapped inside the panel while it is open.

Props

PropTypeDefaultDescription
isOpen*booleanControls whether the panel is visible.
onClose*() => voidCalled when the user dismisses the panel (close button, Escape, or backdrop click).
position"left" | "right""right"Which edge of the screen the panel slides in from.
size"sm" | "md" | "lg""md"Maximum width: sm = 20rem, md = 24rem, lg = 36rem.
titlestringundefinedTitle shown in the header. Also used as the accessible label via aria-labelledby.
descriptionstringundefinedSubtitle shown below the title.
children*React.ReactNodeScrollable panel body content.
footerReact.ReactNodeundefinedContent pinned to the bottom of the panel (ideal for action buttons).
classNamestringundefinedAdditional class on the panel element.
headerClassNamestringundefinedAdditional class on the header section.
titleContainerClassNamestringundefinedAdditional class on the title + description container.
titleClassNamestringundefinedAdditional class on the title element.
descriptionClassNamestringundefinedAdditional class on the description element.
contentClassNamestringundefinedAdditional class on the body section.
closeButtonClassNamestringundefinedAdditional class on the close button.

* Required

Design Guidelines

  • Use SidePanel instead of Modal when the content is long (needs scrolling) or when the user should still be able to reference the main page.
  • Use size="lg" for complex forms or detail views; size="sm" for narrow filters or inspector panels.
  • Always place action buttons in the footer prop — they stay visible regardless of body scroll position.
  • Pair position="left" with navigation or filter UIs; default to right for record details and forms.
  • Best fits: record details, advanced filter forms, multi-step wizards, side-by-side editing.

Accessibility

  • Renders with role="dialog" and aria-modal="true", plus aria-labelledby linked to the title.
  • Focus is trapped inside the panel while open and returned to the originating element on close.
  • Body scroll is locked while the panel is open.
  • Escape, the close button, and backdrop click all call onClose.