Basic
A Drawer.Trigger opens the Drawer.Panel — the all-in-one portal, backdrop, viewport, and draggable popup. Compose the trigger from a Button through render. Inside, a Drawer.Grip gives the panel a visible drag affordance, and a swipe-safe Drawer.Content holds a Drawer.Header (with a Title and Description), a scrollable Drawer.Body, and a Drawer.Footer whose actions close the panel through Drawer.Close. Unlike a dialog there is no corner close button — drawers are dismissed by swiping, the grip, an outside press, or Escape.
"use client";
import { Button } from "@stridge/noctis/button";
import { Drawer } from "@stridge/noctis/drawer";
export default function DrawerBasic() {
return (
<Drawer.Root>
<Drawer.Trigger render={<Button variant="outline">Open drawer</Button>} />
<Drawer.Panel>
<Drawer.Grip />
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>Notifications</Drawer.Title>
<Drawer.Description>You are all caught up. Good job!</Drawer.Description>
</Drawer.Header>
<Drawer.Body>
<p className="text-regular text-muted">
Drag the handle down, press Escape, or tap outside to dismiss. On touch devices you can flick the
panel away.
</p>
</Drawer.Body>
<Drawer.Footer>
<Drawer.Close render={<Button variant="secondary">Close</Button>} />
<Drawer.Close render={<Button variant="primary">Mark all read</Button>} />
</Drawer.Footer>
</Drawer.Content>
</Drawer.Panel>
</Drawer.Root>
);
}
Sides & sizes
Dock the panel to any edge with side on Drawer.Root — bottom (the default), top, start, or end. side is root-owned: the root maps it to the dismiss-swipe direction (start/end flip under RTL) and seeds the descendants, so the viewport and popup position themselves with no extra wiring — and the panel can never render on a different edge than it swipes toward. size on the panel sets the cross-axis extent — sm, md (default), lg, xl, or full — the width cap for side drawers and the height cap for top and bottom, so every size is meaningful on every edge (full lifts the cap).
"use client";
import { Button } from "@stridge/noctis/button";
import { Drawer, type DrawerSide } from "@stridge/noctis/drawer";
const SIDES: { side: DrawerSide; label: string }[] = [
{ side: "bottom", label: "Bottom" },
{ side: "top", label: "Top" },
{ side: "start", label: "Start" },
{ side: "end", label: "End" },
];
export default function DrawerSidesAndSizes() {
return (
<div className="flex flex-wrap gap-3">
{SIDES.map(({ side, label }) => (
<Drawer.Root key={side} side={side}>
<Drawer.Trigger render={<Button variant="outline">{label}</Button>} />
<Drawer.Panel size="md">
{(side === "bottom" || side === "top") && <Drawer.Grip />}
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>{label} drawer</Drawer.Title>
<Drawer.Description>Docked to the {side} edge; swipe that way to dismiss.</Drawer.Description>
</Drawer.Header>
<Drawer.Body>
<p className="text-regular text-muted">
`start` and `end` flip under RTL. `size` is the cross-axis extent — the width cap for side
drawers, the height cap for top and bottom — so it stays meaningful on every edge.
</p>
</Drawer.Body>
</Drawer.Content>
</Drawer.Panel>
</Drawer.Root>
))}
</div>
);
}
Snap points
Pass snapPoints to Drawer.Root to make a bottom sheet snap between preset heights — a compact peek and a near full-height view. Points are fractions of the viewport (0–1), pixel numbers (> 1), or px/rem strings; the full-height 1 stop drives data-expanded. A fast flick can skip past stops; snapToSequentialPoints forces distance-based snapping instead.
"use client";
import { Button } from "@stridge/noctis/button";
import { Drawer } from "@stridge/noctis/drawer";
// A compact peek (14rem) and the full-height stop (`1`). The sheet snaps between them; a fast flick can
// skip straight to the far stop, a slow drag lands on the nearest.
const SNAP_POINTS = ["14rem", 1];
export default function DrawerSnapPoints() {
return (
<Drawer.Root snapPoints={SNAP_POINTS}>
<Drawer.Trigger render={<Button variant="outline">Open snap drawer</Button>} />
<Drawer.Panel>
<Drawer.Grip />
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>Snap points</Drawer.Title>
<Drawer.Description>Drag between a compact peek and a near full-height view.</Drawer.Description>
</Drawer.Header>
<Drawer.Body>
<div className="flex flex-col gap-2">
{Array.from({ length: 12 }, (_, index) => (
<div key={index} className="h-12 rounded-md bg-surface-raised" aria-hidden />
))}
</div>
</Drawer.Body>
<Drawer.Footer>
<Drawer.Close render={<Button variant="secondary">Close</Button>} />
</Drawer.Footer>
</Drawer.Content>
</Drawer.Panel>
</Drawer.Root>
);
}
Nested drawers
Render a Drawer.Root inside another drawer's Drawer.Content to stack them — here three levels deep. Each parent scales back and lifts toward its edge as the next opens, an iOS-style card stack that handles each panel's variable height. Focus and Escape ordering stay correct across the stack, and clicking the dimmed area dismisses the frontmost panel first, peeling the stack back one level at a time. A nested drawer keeps a transparent backdrop as that click-catcher (the visible dim comes from the root drawer's scrim), and each parent's transform reads the live nesting count and frontmost height.
"use client";
import { Button } from "@stridge/noctis/button";
import { Drawer } from "@stridge/noctis/drawer";
// A self-nesting level: each drawer hosts the trigger for the next one inside its own body, so the
// stack fans out as deep as `max`. Parents scale back behind the frontmost panel; clicking the dimmed
// area (or pressing Escape) peels the stack back one level at a time.
function Level({ depth, max }: { depth: number; max: number }) {
const last = depth === max;
return (
<Drawer.Root>
<Drawer.Trigger render={<Button variant={depth === 1 ? "outline" : "secondary"} />}>
{depth === 1 ? "Open drawer" : `Open level ${depth}`}
</Drawer.Trigger>
<Drawer.Panel>
<Drawer.Grip />
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>Level {depth}</Drawer.Title>
<Drawer.Description>
{last
? "The frontmost panel. Click the dimmed area or press Escape to peel back one level."
: "Open the next level — this panel scales back behind it. Clicking the dim closes the topmost first."}
</Drawer.Description>
</Drawer.Header>
<Drawer.Body>{last ? null : <Level depth={depth + 1} max={max} />}</Drawer.Body>
<Drawer.Footer>
<Drawer.Close render={<Button variant="secondary">Back</Button>} />
</Drawer.Footer>
</Drawer.Content>
</Drawer.Panel>
</Drawer.Root>
);
}
export default function DrawerNested() {
return <Level depth={1} max={3} />;
}
Action sheet
A bottom sheet works well as an iOS-style action list: a group of Drawer.Close-backed actions that each dismiss the sheet, with a destructive action and a cancel separated into the footer.
"use client";
import { Button } from "@stridge/noctis/button";
import { Drawer } from "@stridge/noctis/drawer";
const ACTIONS = ["Copy link", "Edit details", "Duplicate", "Add to favorites"];
export default function DrawerActionSheet() {
return (
<Drawer.Root>
<Drawer.Trigger render={<Button variant="outline">Open actions</Button>} />
<Drawer.Panel size="sm">
<Drawer.Grip />
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>Project</Drawer.Title>
<Drawer.Description>Choose an action for this item.</Drawer.Description>
</Drawer.Header>
<Drawer.Body>
<div className="flex flex-col gap-1">
{ACTIONS.map((action) => (
<Drawer.Close
key={action}
render={<Button variant="ghost" fullWidth className="justify-start" />}
>
{action}
</Drawer.Close>
))}
</div>
</Drawer.Body>
{/* Action sheets stack their actions full-width, so keep the footer a column on every
width rather than letting it fall back to the side-by-side row. */}
<Drawer.Footer className="flex-col">
<Drawer.Close render={<Button variant="ghost-danger" fullWidth />}>Delete</Drawer.Close>
<Drawer.Close render={<Button variant="secondary" fullWidth />}>Cancel</Drawer.Close>
</Drawer.Footer>
</Drawer.Content>
</Drawer.Panel>
</Drawer.Root>
);
}
Mobile navigation
A full-height start drawer makes a compact navigation menu — a scrollable list of links, each closing the drawer on selection. Swipe toward the start edge to dismiss.
"use client";
import { Button } from "@stridge/noctis/button";
import { Drawer } from "@stridge/noctis/drawer";
const LINKS = ["Home", "Discover", "Library", "Downloads", "Podcasts", "Settings", "Account", "Help"];
export default function DrawerMobileNav() {
return (
<Drawer.Root side="start">
<Drawer.Trigger render={<Button variant="outline">Open menu</Button>} />
<Drawer.Panel size="sm">
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>Menu</Drawer.Title>
</Drawer.Header>
<Drawer.Body>
<nav className="flex flex-col gap-1">
{LINKS.map((link) => (
// Real destinations, so each item is an anchor — `Drawer.Close` composes a
// ghost `Button` that renders an `<a>`, keeping link semantics while still
// dismissing the drawer on navigation.
<Drawer.Close
key={link}
render={
<Button
variant="ghost"
fullWidth
className="justify-start"
render={<a href={`#${link.toLowerCase()}`} aria-label={link} />}
/>
}
>
{link}
</Drawer.Close>
))}
</nav>
</Drawer.Body>
</Drawer.Content>
</Drawer.Panel>
</Drawer.Root>
);
}
Swipe to open
A Drawer.SwipeArea is an invisible edge strip that opens the drawer when you swipe inward — no visible trigger needed. It sits as a sibling of Drawer.Portal inside Drawer.Root, anchors itself at the docked edge, and pairs with modal={false} so the page stays interactive. Here it is scoped to a card by portalling into a local container.
"use client";
import { Button } from "@stridge/noctis/button";
import { Drawer } from "@stridge/noctis/drawer";
import { useState } from "react";
export default function DrawerSwipeToOpen() {
// Portal into a local container so the non-modal drawer is scoped to this demo card, not the whole
// page. The portaled parts get `absolute` so they anchor to the card instead of the viewport.
const [container, setContainer] = useState<HTMLDivElement | null>(null);
return (
<div ref={setContainer} className="relative h-64 overflow-hidden rounded-lg border border-border bg-surface">
<Drawer.Root side="end" modal={false}>
<Drawer.SwipeArea className="absolute border-s-2 border-dashed border-accent/50 bg-accent/10">
<span
className="absolute end-1.5 top-1/2 -translate-y-1/2 text-small text-muted"
style={{ writingMode: "vertical-rl" }}
>
Swipe
</span>
</Drawer.SwipeArea>
<div className="grid h-full place-items-center px-12 text-center text-small text-muted">
Swipe inward from the docked edge — or use the button — to open the drawer.
<Drawer.Trigger render={<Button variant="outline" size="sm" className="mt-3" />}>Open</Drawer.Trigger>
</div>
<Drawer.Portal container={container}>
<Drawer.Backdrop className="absolute" />
<Drawer.Viewport className="absolute">
<Drawer.Popup size="sm">
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>Library</Drawer.Title>
<Drawer.Description>Jump back into your playlists whenever you want.</Drawer.Description>
</Drawer.Header>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
</div>
);
}
Indent effect
Wrap the app shell in a Drawer.Provider with a Drawer.Indent (and a Drawer.IndentBackground behind it) to make the whole UI scale back and reveal a darker layer when a drawer opens — the "the app sinks back" effect. Portal the drawer into the indented region and use modal={false} so it sits in front of the receding shell; the scale tracks the live drag.
"use client";
import { Button } from "@stridge/noctis/button";
import { Drawer } from "@stridge/noctis/drawer";
import { useState } from "react";
export default function DrawerIndent() {
// The drawer must render *inside* the scaled region, so portal into the indented wrapper.
const [container, setContainer] = useState<HTMLDivElement | null>(null);
return (
<Drawer.Provider>
<div ref={setContainer} className="relative h-72 overflow-hidden rounded-lg">
<Drawer.IndentBackground className="absolute" />
<Drawer.Indent className="grid h-full place-items-center border border-border bg-surface p-8">
<Drawer.Root modal={false}>
<div className="flex flex-col items-center gap-3 text-center text-small text-muted">
Opening the drawer scales this panel back, revealing the layer behind it.
<Drawer.Trigger render={<Button variant="outline" size="sm" />}>Open drawer</Drawer.Trigger>
</div>
<Drawer.Portal container={container}>
<Drawer.Viewport className="absolute">
<Drawer.Popup>
<Drawer.Grip />
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>Inbox</Drawer.Title>
<Drawer.Description>The app sinks back while this is open.</Drawer.Description>
</Drawer.Header>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
</Drawer.Indent>
</div>
</Drawer.Provider>
);
}
Close confirmation
For a drawer with unsaved work, control its open state and call eventDetails.cancel() inside onOpenChange to veto a close, then ask for confirmation through an Alert dialog. Confirming discards the work and closes the drawer for real.
"use client";
import { AlertDialog } from "@stridge/noctis/alert-dialog";
import { Button } from "@stridge/noctis/button";
import { Drawer } from "@stridge/noctis/drawer";
import { Textarea } from "@stridge/noctis/textarea";
import { useState } from "react";
export default function DrawerCloseConfirmation() {
const [drawerOpen, setDrawerOpen] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false);
const [note, setNote] = useState("");
return (
<>
<Drawer.Root
open={drawerOpen}
onOpenChange={(open, eventDetails) => {
// Veto a close that would drop unsaved text, and ask first.
if (!open && note.trim() !== "") {
eventDetails.cancel();
setConfirmOpen(true);
return;
}
setDrawerOpen(open);
}}
>
<Drawer.Trigger render={<Button variant="outline">Compose note</Button>} />
<Drawer.Panel>
<Drawer.Grip />
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>New note</Drawer.Title>
<Drawer.Description>Closing with unsaved text asks for confirmation.</Drawer.Description>
</Drawer.Header>
<Drawer.Body>
<Textarea.Root className="w-full">
<Textarea.Control
aria-label="Note"
rows={4}
value={note}
onValueChange={setNote}
placeholder="Type something, then try to close…"
/>
</Textarea.Root>
</Drawer.Body>
</Drawer.Content>
</Drawer.Panel>
</Drawer.Root>
<AlertDialog.Root open={confirmOpen} onOpenChange={setConfirmOpen}>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>Discard note?</AlertDialog.Title>
<AlertDialog.Description>Your unsaved text will be lost.</AlertDialog.Description>
</AlertDialog.Header>
<AlertDialog.Footer>
<AlertDialog.Cancel render={<Button variant="secondary">Keep editing</Button>} />
<AlertDialog.Action
render={<Button variant="danger">Discard</Button>}
onClick={() => {
setNote("");
setDrawerOpen(false);
}}
/>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>
</>
);
}
Detached triggers
Triggers don't have to live inside Drawer.Root. Create a shared handle with Drawer.createHandle(), pass it to the root and to any Drawer.Trigger anywhere in the tree, and give each trigger a payload — it arrives through the root's function child, so one drawer can render different content per trigger.
"use client";
import { Button } from "@stridge/noctis/button";
import { Drawer } from "@stridge/noctis/drawer";
// A shared handle lets triggers anywhere in the tree drive one drawer, each passing its own payload.
const profileDrawer = Drawer.createHandle<{ name: string; role: string }>();
const PEOPLE = [
{ name: "Ada Lovelace", role: "Mathematician" },
{ name: "Alan Turing", role: "Computer scientist" },
];
export default function DrawerDetachedTriggers() {
return (
<>
<div className="flex flex-wrap gap-3">
{PEOPLE.map((person) => (
<Drawer.Trigger
key={person.name}
handle={profileDrawer}
payload={person}
render={<Button variant="outline" />}
>
{person.name}
</Drawer.Trigger>
))}
</div>
<Drawer.Root handle={profileDrawer} side="end">
{({ payload }) => (
<Drawer.Panel>
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>{payload?.name ?? "Profile"}</Drawer.Title>
<Drawer.Description>{payload?.role ?? "Pick a person to view."}</Drawer.Description>
</Drawer.Header>
</Drawer.Content>
</Drawer.Panel>
)}
</Drawer.Root>
</>
);
}
Theme scope
ThemeScope re-tunes a whole region's shape — corner radius, density, and type scale — through the cascade, with no per-component prop and without touching the app-wide theme. Tweak the knobs below, then open the drawer: the panel re-tunes too, even though it portals to <body>, because the scope rides a portal-safe context. See Customization for nesting, reset, and the raw-token portal caveat.
"use client";
import { DENSITY_PRESETS, FONT_SCALE_PRESETS, RADIUS_PRESETS, ThemeScope } from "@stridge/noctis";
import { Button } from "@stridge/noctis/button";
import { Drawer } from "@stridge/noctis/drawer";
import { Select } from "@stridge/noctis/select";
import { useState } from "react";
/** Title-case a preset key for its select label. */
const label = (key: string) => key.charAt(0).toUpperCase() + key.slice(1);
/** One labelled preset picker — its options are a shipped `ThemeScope` preset map, so the playground's
* vocab is exactly what the primitive accepts. */
function Knob({
name,
presets,
value,
onChange,
}: {
name: string;
presets: Record<string, number>;
value: string;
onChange: (value: string) => void;
}) {
const keys = Object.keys(presets);
const items = Object.fromEntries(keys.map((key) => [key, label(key)]));
return (
<label className="flex flex-col gap-1.5">
<span className="text-mini font-medium text-subtle">{name}</span>
<Select.Root items={items} value={value} onValueChange={(next) => onChange(String(next))}>
<Select.Trigger aria-label={name} className="w-40">
<Select.Value />
<Select.Icon />
</Select.Trigger>
<Select.Popup>
{keys.map((key) => (
<Select.Item key={key} value={key}>
{label(key)}
</Select.Item>
))}
</Select.Popup>
</Select.Root>
</label>
);
}
/**
* A live `ThemeScope` playground for a floating component: the three seed knobs drive a scope wrapping
* the drawer. Open it and the panel re-tunes too — corners, spacing, and text size — even though it
* portals out to `<body>`, because the scope rides a portal-safe context, not just the DOM cascade.
*/
export default function DrawerThemeScope() {
const [radius, setRadius] = useState<keyof typeof RADIUS_PRESETS>("pill");
const [density, setDensity] = useState<keyof typeof DENSITY_PRESETS>("default");
const [fontScale, setFontScale] = useState<keyof typeof FONT_SCALE_PRESETS>("default");
return (
<div className="flex flex-col gap-5">
<div className="flex flex-wrap gap-3">
<Knob name="Radius" presets={RADIUS_PRESETS} value={radius} onChange={(v) => setRadius(v as typeof radius)} />
<Knob
name="Density"
presets={DENSITY_PRESETS}
value={density}
onChange={(v) => setDensity(v as typeof density)}
/>
<Knob
name="Font scale"
presets={FONT_SCALE_PRESETS}
value={fontScale}
onChange={(v) => setFontScale(v as typeof fontScale)}
/>
</div>
<div className="rounded-md border border-dashed border-border p-4">
<ThemeScope radius={radius} density={density} fontScale={fontScale}>
<Drawer.Root>
<Drawer.Trigger render={<Button variant="secondary">Open drawer</Button>} />
<Drawer.Panel>
<Drawer.Grip />
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>Notifications</Drawer.Title>
<Drawer.Description>You are all caught up. Good job!</Drawer.Description>
</Drawer.Header>
<Drawer.Body>
<p className="text-regular text-muted">The panel re-tunes with the scope above.</p>
</Drawer.Body>
<Drawer.Footer>
<Drawer.Close render={<Button variant="primary">Mark all read</Button>} />
</Drawer.Footer>
</Drawer.Content>
</Drawer.Panel>
</Drawer.Root>
</ThemeScope>
</div>
</div>
);
}
Keyboard
| Key | Action |
|---|---|
| Enter / Space | On the trigger: open the drawer and move focus into the panel. |
| Tab / Shift + Tab | Move focus to the next / previous element, trapped within the open panel (when modal). |
| Esc | Close the panel and return focus to the trigger; in a stack, pop the top drawer first. |
A click on the dimmed backdrop also closes the drawer, and on touch devices a swipe toward the docked edge dismisses it. Dismissal is vetoable through onOpenChange's cancel().
Anatomy
Compose a drawer from its parts. Drawer.Root owns the open state and the docked side (it accepts every Base UI Drawer.Root prop — open, defaultOpen, onOpenChange, modal, snapPoints, swipeDirection, handle). Modal by default: focus is trapped, the page is scroll-locked, and the rest of the document is inert.
Drawer.Root— owns the open state, the dockedside, and snap/swipe behaviour; renders no element of its own.Drawer.Trigger— opens the drawer. Style it directly or compose aButtonthroughrender; passhandle/payloadfor detached control.Drawer.Panel— the common composition: portal, backdrop, viewport, and the draggable popup in one. Props:side(defaults from the root),size(defaultmd),backdropClassName,container, andforceRenderBackdrop. Reach forDrawer.Portal+Backdrop+Viewport+Popupdirectly to customize the portal container or backdrop wiring.Drawer.Grip— a decorative drag-handle pill; place it directly under the popup (outsideContent) so it stays a drag target.Drawer.Content— the swipe-safe wrapper for selectable text, scrollable lists, and controls, so pointer interactions there aren't read as dismiss swipes.Drawer.Header— the top region for theTitle,Description, and corner actions, separated from the body by a divider.Drawer.Body— the scrollable middle region; it grows to fill and scrolls its overflow so the header and footer stay put.Drawer.Footer— the bottom region, pinned to the base, typically holding the primary and secondary actions.Drawer.Title+Drawer.Description— the panel's accessible name and supporting copy, linked to the popup viaaria-labelledbyandaria-describedby.Drawer.Close— closes the nearest drawer. A bare button with no styling of its own, so it composes with anyButtonthroughrender— a footer action, or an optional ghost icon in the header gutter. Drawers dismiss primarily by swipe, the grip, an outside press, orEscape, so an explicit close is optional.Drawer.SwipeArea— an invisible edge strip that opens the drawer on an inward swipe.Drawer.Provider/Drawer.Indent/Drawer.IndentBackground— coordinate the app-shell indent effect.
Every rendered part carries a data-slot (noctis-drawer-trigger, noctis-drawer-backdrop, noctis-drawer-viewport, noctis-drawer-popup, noctis-drawer-grip, noctis-drawer-content, noctis-drawer-header, noctis-drawer-body, noctis-drawer-footer, noctis-drawer-title, noctis-drawer-description, noctis-drawer-close, noctis-drawer-swipe-area, noctis-drawer-indent, noctis-drawer-indent-background) for host-side styling — the viewport and popup also carry data-side, and the popup data-size. Pair them with the Base UI state attributes (data-open, data-closed, data-starting-style, data-ending-style, data-swiping, data-swipe-direction, data-expanded, data-nested-drawer-open, data-nested). The popup renders through Surface at elevated elevation, so controls inside re-derive off that base and separate cleanly.
Design tokens
Generated from the component's declaration — the same graph that mints the CSS, so a variable name or its resolution default can't drift. The minted tokens are the public override seam: set one on any ancestor and every drawer in that region retunes — e.g. .app { --noctis-drawer-popup-peek-offset: 1.5rem; } widens how far stacked panels fan out beneath it. Knobs that aren't minted are reached through the part's data-slot. See Customization for the full override ladder and Tokens for the whole graph.
API reference
Generated from the component's types — every prop, type, default, and description comes straight from the source. Each part gets its own table; parts that only forward to Base UI's Drawer list just the props they pass through. Expand a row for the full type and description.
Drawer.Root
Drawer.Trigger
Drawer.Portal
Drawer.Backdrop
Drawer.Viewport
Drawer.Popup
Drawer.Panel
Drawer.Grip
Drawer.Content
Drawer.Header
Drawer.Body
Drawer.Footer
Drawer.Title
Drawer.Description
Drawer.Close
Drawer.SwipeArea
Drawer.Provider
No props of its own — forwards to the underlying Base UI part.