Drawer

Shows edge sheets for navigation or inspection, with focus management and swipe dismissal.

import { Drawer } from "@usefragments/ui";Source

Preview

<Drawer>
  <Drawer.Trigger render={<Button variant="soft" />}>Edit</Drawer.Trigger>
  <Drawer.Content>
    <Drawer.Header>
      <Drawer.Title>Edit repository</Drawer.Title>
      <Drawer.Description>Changes apply on the next check.</Drawer.Description>

Usage

Use when

  • Editing or creating alongside the page behind it
  • Touch-reachable action sheets
  • Navigation that slides in and back out
  • Detail views that overlay rather than replace

Avoid when

  • A centred task (use Dialog)
  • Navigation that stays on screen (use Sidebar)
  • A short list of actions (use Menu or Popover)
  • Feedback the user does not act on (use Toast)

Implementation notes

  • end for editing and inspecting, start for navigation, bottom for touch
  • start and end follow the writing direction; the swipe that dismisses follows the side
  • Always give a visible way out: Drawer.Close in the header or footer
  • One task per drawer
  • Pass render={<Button />} to Drawer.Trigger or Drawer.Close to use a library control
  • Use modal="trap-focus" for an inspector that should not dim the page

Installation

pnpm add @usefragments/ui

The npm package stays managed by your package manager. Import styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.

Anatomy

Import Drawer once, then compose its parts with dot notation.

  • Drawer.Trigger
  • Drawer.Content
  • Drawer.Close
  • Drawer.SwipeArea
  • Drawer.Header
  • Drawer.Title
  • Drawer.Description
  • Drawer.Body
  • Drawer.Footer

Enters on the end edge, the default for editing and inspecting.

<Drawer>
  <Drawer.Trigger render={<Button variant="soft" />}>Edit</Drawer.Trigger>
  <Drawer.Content>
    <Drawer.Header>
      <Drawer.Title>Edit repository</Drawer.Title>
      <Drawer.Description>Changes apply on the next check.</Drawer.Description>
      <Drawer.Close />
    </Drawer.Header>
    <Drawer.Body>
      <p>Drawer content goes here.</p>
    </Drawer.Body>
    <Drawer.Footer>
      <Drawer.Close render={<Button variant="ghost" />}>Cancel</Drawer.Close>
      <Button variant="solid">Save</Button>
    </Drawer.Footer>
  </Drawer.Content>
</Drawer>

Examples

Start Side

Enters on the start edge, where navigation belongs.

<Drawer>
  <Drawer.Trigger render={<Button variant="soft" />}>Menu</Drawer.Trigger>
  <Drawer.Content side="start" size="sm">
    <Drawer.Header>
      <Drawer.Title>Navigation</Drawer.Title>
      <Drawer.Close />

Bottom Sheet

Rises from the bottom for touch-reachable actions, with snap points.

<Drawer snapPoints={[0.4, 1]}>
  <Drawer.Trigger render={<Button variant="soft" />}>Actions</Drawer.Trigger>
  <Drawer.Content side="bottom" size="lg">
    <Drawer.Header>
      <Drawer.Title>Actions</Drawer.Title>
    </Drawer.Header>

Inspector

Keeps focus inside without dimming the page.

<Drawer modal="trap-focus">
  <Drawer.Trigger render={<Button variant="soft" />}>Inspect</Drawer.Trigger>
  <Drawer.Content size="sm">
    <Drawer.Header>
      <Drawer.Title>Details</Drawer.Title>
      <Drawer.Close />

API reference

Component props
Prop
Type
Default
Description
Body.childrenRequiredReact.ReactNodeNot set—
childrenRequiredReact.ReactNodeNot setDrawer parts (Drawer.Trigger, Drawer.Content)
Content.childrenRequiredReact.ReactNodeNot set—
Content.finalFocusboolean | React.RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | HTMLElement | null | void) | undefinedNot setWhere focus goes when the drawer closes.
Content.initialFocusboolean | React.RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | HTMLElement | null | void) | undefinedNot setWhere focus goes when the drawer opens.
Content.sideDrawerSide | undefinedstartendbottomNot setThe edge the drawer sits on. `start` and `end` follow the writing direction.
Content.sizeDrawerSize | undefinedsmmdlgNot setWidth on the start and end edges, height on the bottom edge.
defaultOpenboolean | undefinedfalseDefault open state (uncontrolled)
Description.childrenRequiredReact.ReactNodeNot set—
disablePointerDismissalboolean | undefinedfalseKeep the drawer open on an outside press
Footer.childrenRequiredReact.ReactNodeNot set—
Header.childrenRequiredReact.ReactNodeNot set—
modalboolean | "trap-focus" | undefinedtruetrue draws the scrim and blocks the page; trap-focus keeps focus inside without a scrim; false leaves the page usable
onOpenChange((open: boolean) => void) | undefinedNot setCalled when the open state changes
onOpenChangeComplete((open: boolean) => void) | undefinedNot setCalled after the open or close transition finishes
openboolean | undefinedNot setControlled open state
snapPoints(string | number)[] | undefinedNot setSnap points for a bottom sheet: viewport fractions, pixels, or px/rem strings
SwipeArea.disabledboolean | undefinedNot setDisable swipe-to-open
Title.childrenRequiredReact.ReactNodeNot set—

Accessibility

  • Traps focus within the drawer
  • Closes on Escape
  • Returns focus to the trigger on close
  • role="dialog" labelled by Drawer.Title
  • A body that overflows joins the tab order so it scrolls from the keyboard
  • DialogsiblingUse Dialog for centered modal overlays
  • SidebaralternativeUse Sidebar for permanent side navigation
  • PopoveralternativeUse Popover for small contextual content

Next steps