Drawer
A panel that slides in from the edge of the screen.
import { Drawer } from 'base-ui-solid/drawer';
import styles from './index.module.css';
export default function ExampleDrawer() {
return (
<Drawer.Root swipeDirection="right">
<Drawer.Trigger class={styles.Button}>Open drawer</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Backdrop class={styles.Backdrop} />
<Drawer.Viewport class={styles.Viewport}>
<Drawer.Popup class={styles.Popup}>
<Drawer.Content class={styles.Content}>
<Drawer.Title class={styles.Title}>Drawer</Drawer.Title>
<Drawer.Description class={styles.Description}>
This is a drawer that slides in from the side. You can swipe to dismiss it.
</Drawer.Description>
<div class={styles.Actions}>
<Drawer.Close class={styles.Button}>Close</Drawer.Close>
</div>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
);
}
Usage guidelines
- Drawer extends Dialog: It adds gesture support, snap points, and indent effects. If you don’t need these, use Dialog instead. A panel that slides in from the edge of the screen and doesn’t need gesture support is a positioned Dialog.
Anatomy
Import the component and assemble its parts:
import { Drawer } from 'base-ui-solid/drawer';
<Drawer.Provider>
<Drawer.IndentBackground />
<Drawer.Indent>
<Drawer.Root>
<Drawer.Trigger />
<Drawer.SwipeArea />
<Drawer.Portal>
<Drawer.Backdrop />
<Drawer.Viewport>
<Drawer.Popup>
<Drawer.Content>
<Drawer.Title />
<Drawer.Description />
<Drawer.Close />
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
</Drawer.Indent>
</Drawer.Provider>;
Drawer supports swipe gestures to dismiss. Set swipeDirection to control which direction dismisses the drawer. <Drawer.Content> allows text selection of its children without swipe interference when using a mouse pointer.
Add data-base-ui-swipe-ignore to a descendant to opt it out of swipe dismissal for all input types. If the element only handles touch drags along one axis, such as a JavaScript carousel, set the value to x or y so touch drags along the other axis still swipe the drawer.
Use <Drawer.VirtualKeyboardProvider> when a bottom sheet contains form fields and you want Base UI to manage keyboard-aware focus and scroll handling for software keyboards. Drawers without this provider are unaffected.
Examples
State
By default, Drawer is an uncontrolled component that manages its own state.
<Drawer.Root>
<Drawer.Trigger>Open</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Viewport>
<Drawer.Popup>
<Drawer.Content>
<Drawer.Title>Example drawer</Drawer.Title>
<Drawer.Close>Close</Drawer.Close>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
Use open and onOpenChange props if you need to access or control the state of the drawer.
import { createSignal } from 'solid-js';
const [open, setOpen] = createSignal(false);
return (
<Drawer.Root open={open()} onOpenChange={setOpen}>
<Drawer.Trigger>Open</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Viewport>
<Drawer.Popup>
<Drawer.Content>
<Drawer.Title>Example drawer</Drawer.Title>
<Drawer.Close>Close</Drawer.Close>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
);
Position
Positioning is handled by your styles. swipeDirection defaults to "down" for bottom sheets. Use "up", "left", or "right" for other drawer positions.
<Drawer.Root swipeDirection="right">
import { Drawer } from 'base-ui-solid/drawer';
import styles from './index.module.css';
export default function ExampleDrawer() {
return (
<Drawer.Root>
<Drawer.Trigger class={styles.Button}>Open bottom drawer</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Backdrop class={styles.Backdrop} />
<Drawer.Viewport class={styles.Viewport}>
<Drawer.Popup class={styles.Popup}>
<div class={styles.Handle} />
<Drawer.Content class={styles.Content}>
<Drawer.Title class={styles.Title}>Notifications</Drawer.Title>
<Drawer.Description class={styles.Description}>
You are all caught up. Good job!
</Drawer.Description>
<div class={styles.Actions}>
<Drawer.Close class={styles.Button}>Close</Drawer.Close>
</div>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
);
}
Nested drawers
Use the [data-nested-drawer-open] selector and the --nested-drawers CSS variable to style drawers when a nested drawer is open.
This demo stacks nested drawers using a constant peek so the frontmost drawer stays anchored to the bottom while the ones behind it are scaled down and lifted. It also uses the --drawer-height and --drawer-frontmost-height CSS variables to handle varying drawer heights.
import { Drawer } from 'base-ui-solid/drawer';
import styles from './index.module.css';
export default function ExampleDrawerNested() {
return (
<Drawer.Root>
<Drawer.Trigger class={styles.Button}>Open drawer stack</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Backdrop class={styles.Backdrop} />
<Drawer.Viewport class={styles.Viewport}>
<Drawer.Popup class={styles.Popup}>
<div class={styles.Handle} />
<Drawer.Content class={styles.Content}>
<Drawer.Title class={styles.Title}>Account</Drawer.Title>
<Drawer.Description class={styles.Description}>
Nested drawers can be styled to stack, while each drawer remains independently focus
managed.
</Drawer.Description>
<div class={styles.Actions}>
<div class={styles.ActionsLeft}>
<Drawer.Root>
<Drawer.Trigger class={styles.Button}>Security settings</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Viewport class={styles.Viewport}>
<Drawer.Popup class={styles.Popup}>
<div class={styles.Handle} />
<Drawer.Content class={styles.Content}>
<Drawer.Title class={styles.Title}>Security</Drawer.Title>
<Drawer.Description class={styles.Description}>
Review sign-in activity and update your security preferences.
</Drawer.Description>
<ul class={styles.List}>
<li>Passkeys enabled</li>
<li>2FA via authenticator app</li>
<li>3 signed-in devices</li>
</ul>
<div class={styles.Actions}>
<div class={styles.ActionsLeft}>
<Drawer.Root>
<Drawer.Trigger class={styles.Button}>
Advanced options
</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Viewport class={styles.Viewport}>
<Drawer.Popup class={styles.Popup}>
<div class={styles.Handle} />
<Drawer.Content class={styles.Content}>
<Drawer.Title class={styles.Title}>Advanced</Drawer.Title>
<Drawer.Description class={styles.Description}>
This drawer is taller to demonstrate variable-height
stacking.
</Drawer.Description>
<div class={styles.Field}>
<label class={styles.Label} for="device-name">
Device name
</label>
<input
id="device-name"
class={styles.Input}
defaultValue="Personal laptop"
/>
</div>
<div class={styles.Field}>
<label class={styles.Label} for="notes">
Notes
</label>
<textarea
id="notes"
class={styles.Textarea}
defaultValue="Rotate recovery codes and revoke older sessions."
rows={3}
/>
</div>
<div class={styles.Actions}>
<Drawer.Close class={styles.Button}>Done</Drawer.Close>
</div>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
</div>
<Drawer.Close class={styles.Button}>Close</Drawer.Close>
</div>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
</div>
<Drawer.Close class={styles.Button}>Close</Drawer.Close>
</div>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
);
}
Snap points
Use snapPoints to snap a bottom sheet drawer to preset heights. Numbers between 0 and 1 represent fractions of the viewport height, and numbers greater than 1 are treated as pixel values. String values support px and rem units (for example, '148px' or '30rem').
const snapPoints = ['148px', 1];
const [snapPoint, setSnapPoint] = createSignal<Drawer.Root.SnapPoint | null>(snapPoints[0]);
<Drawer.Root snapPoints={snapPoints} snapPoint={snapPoint()} onSnapPointChange={setSnapPoint}>
{/* ... */}
</Drawer.Root>;
Apply the snap point offset in your styles when using vertical drawers:
.DrawerPopup {
transform: translateY(calc(var(--drawer-snap-point-offset) + var(--drawer-swipe-movement-y)));
}
import type { JSX } from '@solidjs/web';
import { Drawer } from 'base-ui-solid/drawer';
import styles from './index.module.css';
const TOP_MARGIN_REM = 1;
const VISIBLE_SNAP_POINTS_REM = [30];
function toViewportSnapPoint(heightRem: number) {
return `${heightRem + TOP_MARGIN_REM}rem`;
}
const snapPoints = [...VISIBLE_SNAP_POINTS_REM.map(toViewportSnapPoint), 1];
export default function ExampleDrawerSnapPoints() {
return (
<Drawer.Root snapPoints={snapPoints}>
<Drawer.Trigger class={styles.Button}>Open snap drawer</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Backdrop class={styles.Backdrop} />
<Drawer.Viewport class={styles.Viewport}>
<Drawer.Popup
class={styles.Popup}
style={{ '--top-margin': `${TOP_MARGIN_REM}rem` } as JSX.CSSProperties}
>
<div class={styles.DragArea}>
<div class={styles.Handle} />
<Drawer.Title class={styles.Title}>Snap points</Drawer.Title>
</div>
<Drawer.Content class={styles.Scroll}>
<div class={styles.Content}>
<Drawer.Description class={styles.Description}>
Drag the sheet to snap between a compact peek and a near full-height view.
</Drawer.Description>
<div class={styles.Cards} aria-hidden="true">
{Array.from({ length: 20 }, (_, _index) => (
<div class={styles.Card} />
))}
</div>
<div class={styles.Actions}>
<Drawer.Close class={styles.Button}>Close</Drawer.Close>
</div>
</div>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
);
}
By default, the drawer can skip snap points when swiping quickly. Specify the snapToSequentialPoints prop to disable velocity-based skipping so the snap target is determined by drag distance (you can still drag past multiple points).
Virtual keyboard aware
Wrap a bottom sheet in <Drawer.VirtualKeyboardProvider> to make it react to software keyboards when it contains form controls. When the keyboard opens, the provider scrolls the body to keep the focused field visible.
- Keep the popup frame stable: place header and footer content outside a plain scrollable body.
- Lift a pinned footer input: if a footer contains its own input, reserve a footer slot below the scroll area and offset it by
var(--drawer-keyboard-inset, 0px). The demo switches the focused footer toposition: fixed— positioned against the popup, since itstransformcontains fixed descendants — and adds the inset to its bottom padding. - Always include the
0pxfallback: the provider only sets--drawer-keyboard-insetwhile the keyboard is aligned, so a barevar(--drawer-keyboard-inset)is invalid before the first alignment and after cleanup.
<Drawer.Root>
<Drawer.VirtualKeyboardProvider>{/* ... */}</Drawer.VirtualKeyboardProvider>
</Drawer.Root>
import { For } from 'solid-js';
import { Drawer } from 'base-ui-solid/drawer';
import styles from './index.module.css';
const fields = [
['Name', 'Ada Lovelace'],
['Phone', '+1 (555) 123-4567'],
['Street address', '12 Computing Way'],
['Apartment', 'Unit 4B'],
['City', 'San Francisco'],
['Postal code', '94107'],
['Delivery window', 'After 6 PM'],
['Backup contact', 'Grace Hopper'],
];
export default function ExampleDrawerVirtualKeyboardAware() {
return (
<Drawer.Root>
<Drawer.Trigger class={styles.Button}>Open keyboard-aware drawer</Drawer.Trigger>
<Drawer.VirtualKeyboardProvider>
<Drawer.Portal>
<Drawer.Backdrop class={styles.Backdrop} />
<Drawer.Viewport class={styles.Viewport}>
<Drawer.Popup class={styles.Popup}>
<div class={styles.Header}>
<div class={styles.Handle} />
<div class={styles.HeaderActions}>
<Drawer.Close class={`${styles.Button} ${styles.HeaderButton}`}>
Cancel
</Drawer.Close>
<Drawer.Title class={styles.Title}>Delivery details</Drawer.Title>
<Drawer.Close class={`${styles.Button} ${styles.HeaderButton}`}>
Save
</Drawer.Close>
</div>
</div>
<Drawer.Content class={styles.Scroll}>
<div class={styles.Form}>
<For each={fields}>
{([label, placeholder]) => (
<label class={styles.Field}>
<span class={styles.FieldLabel}>{label}</span>
<input class={styles.Input} placeholder={placeholder} type="text" />
</label>
)}
</For>
<label class={styles.Field}>
<span class={styles.FieldLabel}>Instructions</span>
<textarea
class={styles.Textarea}
placeholder="Gate code, drop-off spot, or anything else the driver should know"
/>
</label>
</div>
</Drawer.Content>
<div class={styles.FooterSlot}>
<div class={styles.StickyFooter}>
<label class={styles.Composer}>
<span class={styles.FieldLabel}>Delivery note</span>
<input
class={styles.ComposerInput}
placeholder="Add a note for the driver"
type="text"
/>
</label>
</div>
</div>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.VirtualKeyboardProvider>
</Drawer.Root>
);
}
Indent effect
Scale the background down when any drawer opens by wrapping your app in <Drawer.Provider> and use <Drawer.IndentBackground> + <Drawer.Indent> at the top of your tree. Any <Drawer.Root> within the provider notifies it when it mounts, which activates the indent parts (they receive [data-active] state attributes).
import { createSignal } from 'solid-js';
import { Drawer } from 'base-ui-solid/drawer';
import styles from './index.module.css';
export default function ExampleDrawer() {
const [portalContainer, setPortalContainer] = createSignal<HTMLDivElement | null>(null);
return (
<Drawer.Provider>
<div class={styles.Root} ref={setPortalContainer}>
<Drawer.IndentBackground class={styles.IndentBackground} />
<Drawer.Indent class={styles.Indent}>
<div class={styles.Center}>
<Drawer.Root modal={false}>
<Drawer.Trigger class={styles.Button}>Open drawer</Drawer.Trigger>
<Drawer.Portal container={portalContainer()}>
<Drawer.Backdrop class={styles.Backdrop} />
<Drawer.Viewport class={styles.Viewport}>
<Drawer.Popup class={styles.Popup}>
<div class={styles.Handle} />
<Drawer.Content class={styles.Content}>
<Drawer.Title class={styles.Title}>Notifications</Drawer.Title>
<Drawer.Description class={styles.Description}>
You are all caught up. Good job!
</Drawer.Description>
<div class={styles.Actions}>
<Drawer.Close class={styles.Button}>Close</Drawer.Close>
</div>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
</div>
</Drawer.Indent>
</div>
</Drawer.Provider>
);
}
Non-modal
Set modal={false} to opt out of focus trapping and disablePointerDismissal to keep the drawer open on outside clicks.
import { Drawer } from 'base-ui-solid/drawer';
import styles from './index.module.css';
export default function ExampleDrawer() {
return (
<Drawer.Root swipeDirection="right" modal={false} disablePointerDismissal>
<Drawer.Trigger class={styles.Button}>Open non-modal drawer</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Viewport class={styles.Viewport}>
<Drawer.Popup class={styles.Popup}>
<Drawer.Content class={styles.Content}>
<Drawer.Title class={styles.Title}>Non-modal drawer</Drawer.Title>
<Drawer.Description class={styles.Description}>
This drawer does not trap focus and ignores outside clicks. Use the close button or
swipe to dismiss it.
</Drawer.Description>
<div class={styles.Actions}>
<Drawer.Close class={styles.Button}>Close</Drawer.Close>
</div>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
);
}
Mobile navigation
You can build a full-screen mobile navigation sheet using Drawer parts, including a flick-to-dismiss from the top gesture.
import { For } from 'solid-js';
import type { JSX } from '@solidjs/web';
import { Drawer } from 'base-ui-solid/drawer';
import { ScrollArea } from 'base-ui-solid/scroll-area';
import styles from './index.module.css';
const ITEMS = [
{ href: '/solid/overview', label: 'Overview' },
{ href: '/solid/components', label: 'Components' },
{ href: '/solid/utils', label: 'Utilities' },
{ href: '/solid/overview/releases', label: 'Releases' },
] as const;
const LONG_LIST = [
{ href: '/solid/components/accordion', label: 'Accordion' },
{ href: '/solid/components/alert-dialog', label: 'Alert Dialog' },
{ href: '/solid/components/autocomplete', label: 'Autocomplete' },
{ href: '/solid/components/avatar', label: 'Avatar' },
{ href: '/solid/components/button', label: 'Button' },
{ href: '/solid/components/checkbox', label: 'Checkbox' },
{ href: '/solid/components/checkbox-group', label: 'Checkbox Group' },
{ href: '/solid/components/collapsible', label: 'Collapsible' },
{ href: '/solid/components/combobox', label: 'Combobox' },
{ href: '/solid/components/context-menu', label: 'Context Menu' },
{ href: '/solid/components/dialog', label: 'Dialog' },
{ href: '/solid/components/drawer', label: 'Drawer' },
{ href: '/solid/components/field', label: 'Field' },
{ href: '/solid/components/fieldset', label: 'Fieldset' },
{ href: '/solid/components/form', label: 'Form' },
{ href: '/solid/components/input', label: 'Input' },
{ href: '/solid/components/menu', label: 'Menu' },
{ href: '/solid/components/menubar', label: 'Menubar' },
{ href: '/solid/components/meter', label: 'Meter' },
{ href: '/solid/components/navigation-menu', label: 'Navigation Menu' },
{ href: '/solid/components/number-field', label: 'Number Field' },
{ href: '/solid/components/otp-field', label: 'OTP Field' },
{ href: '/solid/components/popover', label: 'Popover' },
{ href: '/solid/components/preview-card', label: 'Preview Card' },
{ href: '/solid/components/progress', label: 'Progress' },
{ href: '/solid/components/radio-group', label: 'Radio Group' },
{ href: '/solid/components/scroll-area', label: 'Scroll Area' },
{ href: '/solid/components/select', label: 'Select' },
{ href: '/solid/components/separator', label: 'Separator' },
{ href: '/solid/components/slider', label: 'Slider' },
{ href: '/solid/components/switch', label: 'Switch' },
{ href: '/solid/components/tabs', label: 'Tabs' },
{ href: '/solid/components/toast', label: 'Toast' },
{ href: '/solid/components/toggle', label: 'Toggle' },
{ href: '/solid/components/toggle-group', label: 'Toggle Group' },
{ href: '/solid/components/toolbar', label: 'Toolbar' },
{ href: '/solid/components/tooltip', label: 'Tooltip' },
] as const;
export default function ExampleDrawerMobileNav() {
return (
<Drawer.Root>
<Drawer.Trigger class={styles.Button}>Open mobile menu</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Backdrop class={styles.Backdrop} />
<Drawer.Viewport class={styles.Viewport}>
<ScrollArea.Root style={{ position: undefined }} class={styles.ScrollAreaRoot}>
<ScrollArea.Viewport class={styles.ScrollAreaViewport}>
<ScrollArea.Content class={styles.ScrollContent}>
<Drawer.Popup class={styles.Popup}>
<nav aria-label="Navigation" class={styles.Panel}>
<div class={styles.Header}>
<div aria-hidden="true" class={styles.HeaderSpacer} />
<div class={styles.Handle} />
<Drawer.Close aria-label="Close menu" class={styles.CloseButton}>
<XIcon />
</Drawer.Close>
</div>
<Drawer.Content class={styles.Content}>
<Drawer.Title class={styles.Title}>Menu</Drawer.Title>
<Drawer.Description class={styles.Description}>
Scroll the long list. Flick down from the top to dismiss.
</Drawer.Description>
<div class={styles.ScrollArea}>
<ul class={styles.List}>
<For each={ITEMS}>
{(item) => (
<li class={styles.Item}>
<a class={styles.Link} href={item.href}>
{item.label}
</a>
</li>
)}
</For>
</ul>
<ul class={styles.LongList} aria-label="Component links">
<For each={LONG_LIST}>
{(item) => (
<li class={styles.Item}>
<a class={styles.Link} href={item.href}>
{item.label}
</a>
</li>
)}
</For>
</ul>
</div>
</Drawer.Content>
</nav>
</Drawer.Popup>
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar class={styles.Scrollbar}>
<ScrollArea.Thumb class={styles.ScrollbarThumb} />
</ScrollArea.Scrollbar>
</ScrollArea.Root>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
);
}
function XIcon(props: JSX.IntrinsicElements['svg']) {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
stroke-linecap="square"
stroke-linejoin="round"
{...props}
style={
typeof props.style === 'string'
? `display: block; ${props.style}`
: { display: 'block', ...(typeof props.style === 'object' ? props.style : {}) }
}
>
<path d="m2.5 2.5 11 11m-11 0 11-11" />
</svg>
);
}
Swipe to open
Place <Drawer.SwipeArea> along the edge of the viewport to enable swipe-to-open gestures.
Swipe from the right edge to open the drawer.
import { createSignal } from 'solid-js';
import { Drawer } from 'base-ui-solid/drawer';
import styles from './index.module.css';
export default function ExampleDrawerSwipeArea() {
const [portalContainer, setPortalContainer] = createSignal<HTMLDivElement | null>(null);
return (
<div class={styles.Root} ref={setPortalContainer}>
<Drawer.Root swipeDirection="right" modal={false}>
<Drawer.SwipeArea class={styles.SwipeArea}>
<span class={styles.SwipeLabel}>Swipe here</span>
</Drawer.SwipeArea>
<div class={styles.Center}>
<div class={styles.Instructions}>
<p class={styles.Hint}>Swipe from the right edge to open the drawer.</p>
</div>
</div>
<Drawer.Portal container={portalContainer()}>
<Drawer.Backdrop class={styles.Backdrop} />
<Drawer.Viewport class={styles.Viewport}>
<Drawer.Popup class={styles.Popup}>
<Drawer.Content class={styles.Content}>
<Drawer.Title class={styles.Title}>Library</Drawer.Title>
<Drawer.Description class={styles.Description}>
Swipe from the edge whenever you want to jump back into your playlists.
</Drawer.Description>
<div class={styles.Actions}>
<Drawer.Close class={styles.Button}>Close</Drawer.Close>
</div>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
</div>
);
}
Close confirmation
This example shows a nested confirmation dialog that opens if the text entered in the drawer is going to be discarded.
To implement this, both the drawer and the confirmation dialog should be controlled. The confirmation dialog may be opened when the onOpenChange callback of the drawer receives a request to close while there is text in the textarea. This way, the confirmation is automatically shown when the user clicks the backdrop, presses the Esc key, clicks a close button, or dismisses the drawer with a swipe gesture.
Use eventDetails.cancel() in onOpenChange to prevent the drawer from closing while the confirmation prompt is shown.
import { createSignal, createUniqueId } from 'solid-js';
import { AlertDialog } from 'base-ui-solid/alert-dialog';
import { Drawer } from 'base-ui-solid/drawer';
import styles from './index.module.css';
export default function ExampleDrawer() {
const [drawerOpen, setDrawerOpen] = createSignal(false);
const [confirmationOpen, setConfirmationOpen] = createSignal(false);
const [textareaValue, setTextareaValue] = createSignal('');
const titleId = createUniqueId();
return (
<Drawer.Root
swipeDirection="right"
open={drawerOpen()}
onOpenChange={(open, eventDetails) => {
// Show the close confirmation if there’s text in the textarea
if (!open && textareaValue()) {
eventDetails.cancel();
setConfirmationOpen(true);
return;
}
if (!open) {
// Reset the textarea value
setTextareaValue('');
}
setDrawerOpen(open);
}}
>
<Drawer.Trigger class={styles.Button}>Tweet</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Backdrop class={styles.Backdrop} />
<Drawer.Viewport class={styles.Viewport}>
<Drawer.Popup
class={`${styles.Popup} ${confirmationOpen() ? styles.PopupDimmed : ''}`.trim()}
>
<Drawer.Content class={styles.Content}>
<Drawer.Title id={titleId} class={styles.Title}>
New tweet
</Drawer.Title>
<form
class={styles.TextareaContainer}
onSubmit={(event) => {
event.preventDefault();
// Close the drawer when submitting
setTextareaValue('');
setDrawerOpen(false);
}}
>
<textarea
aria-labelledby={titleId}
required
class={styles.Textarea}
placeholder="What’s on your mind?"
value={textareaValue()}
onInput={(event) => setTextareaValue(event.target.value)}
/>
<div class={styles.Actions}>
<Drawer.Close class={styles.Button}>Cancel</Drawer.Close>
<button type="submit" class={styles.Button}>
Tweet
</button>
</div>
</form>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
{/* Confirmation dialog */}
<AlertDialog.Root open={confirmationOpen()} onOpenChange={setConfirmationOpen}>
<AlertDialog.Portal>
<AlertDialog.Popup class={styles.AlertPopup}>
<div class={styles.Intro}>
<AlertDialog.Title class={styles.Title}>Discard tweet?</AlertDialog.Title>
<AlertDialog.Description class={styles.Description}>
Your tweet will be lost.
</AlertDialog.Description>
</div>
<div class={styles.Actions}>
<AlertDialog.Close class={styles.Button}>Go back</AlertDialog.Close>
<button
type="button"
class={styles.Button}
onClick={() => {
setConfirmationOpen(false);
setTextareaValue('');
setDrawerOpen(false);
}}
>
Discard
</button>
</div>
</AlertDialog.Popup>
</AlertDialog.Portal>
</AlertDialog.Root>
</Drawer.Root>
);
}
Action sheet with separate destructive action
This demo builds an action sheet with a grouped list of actions plus a separate destructive action button.
import { createSignal, For } from 'solid-js';
import { Drawer } from 'base-ui-solid/drawer';
import styles from './index.module.css';
const ACTIONS = ['Unfollow', 'Mute', 'Add to Favourites', 'Add to Close Friends', 'Restrict'];
export default function ExampleDrawerUncontained() {
const [open, setOpen] = createSignal(false);
return (
<Drawer.Root open={open()} onOpenChange={setOpen}>
<Drawer.Trigger class={styles.Button}>Open action sheet</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Backdrop class={styles.Backdrop} />
<Drawer.Viewport class={styles.Viewport}>
<Drawer.Popup class={styles.Popup}>
<Drawer.Content class={styles.Surface}>
<Drawer.Title class={styles.VisuallyHidden}>Profile actions</Drawer.Title>
<Drawer.Description class={styles.VisuallyHidden}>
Choose an action for this user.
</Drawer.Description>
<ul class={styles.Actions} aria-label="Profile actions">
<For each={ACTIONS}>
{(action, index) => (
<li class={styles.Action}>
{index() === 0 && (
<Drawer.Close class={styles.VisuallyHidden}>
Close action sheet
</Drawer.Close>
)}
<button
type="button"
class={styles.ActionButton}
onClick={() => setOpen(false)}
>
{action}
</button>
</li>
)}
</For>
</ul>
</Drawer.Content>
<div class={styles.DangerSurface}>
<button type="button" class={styles.DangerButton} onClick={() => setOpen(false)}>
Block User
</button>
</div>
</Drawer.Popup>
</Drawer.Viewport>
</Drawer.Portal>
</Drawer.Root>
);
}
Detached triggers
A drawer can be controlled by a trigger located either inside or outside the <Drawer.Root> component. For simple, one-off interactions, place the <Drawer.Trigger> inside <Drawer.Root>.
However, if defining the drawer’s content next to its trigger is not practical, you can use a detached trigger. This involves placing the <Drawer.Trigger> outside of <Drawer.Root> and linking them with a handle created by the Drawer.createHandle() function.
The imperative methods on the handle, such as open() and openWithPayload(), require a <Drawer.Root> using the same handle to be mounted.
Calls made while no root is attached to the handle — before one mounts, or after it unmounts — are ignored. Each time a root mounts, it starts from fresh state: a call made while no root was attached is not replayed, and no open state carries over from a previous mount.
const demoDrawer = Drawer.createHandle();
<Drawer.Trigger handle={demoDrawer}>Open</Drawer.Trigger>
<Drawer.Root handle={demoDrawer}>
...
</Drawer.Root>
The drawer can render different content depending on which trigger opened it. This is achieved by passing a payload to the <Drawer.Trigger> and using the function-as-a-child pattern in <Drawer.Root>.
const demoDrawer = Drawer.createHandle<{ title: string }>();
<Drawer.Trigger handle={demoDrawer} payload={{ title: 'Profile' }}>
Profile
</Drawer.Trigger>
<Drawer.Trigger handle={demoDrawer} payload={{ title: 'Settings' }}>
Settings
</Drawer.Trigger>
<Drawer.Root handle={demoDrawer}>
{({ payload }) => (
<Drawer.Portal>
<Drawer.Popup>
<Drawer.Content>
<Drawer.Title>{payload?.title}</Drawer.Title>
</Drawer.Content>
</Drawer.Popup>
</Drawer.Portal>
)}
</Drawer.Root>
Stacking and animations
Use CSS transitions or animations to animate drawer opening, closing, swipe interactions, and nested stacking. The data-starting-style attribute is applied when a drawer starts to open, and data-ending-style is applied when it starts to close.
The --nested-drawers CSS variable can be used to determine stack depth. The frontmost drawer has index 0.
.DrawerPopup {
--stack-step: 0.05;
--stack-scale: calc(1 - (var(--nested-drawers) * var(--stack-step)));
transform: translateY(var(--drawer-swipe-movement-y)) scale(var(--stack-scale));
}
When stacked drawers have varying heights, use the --drawer-height and --drawer-frontmost-height variables to keep collapsed drawers aligned with the frontmost one.
.DrawerPopup {
--bleed: 3rem;
--stack-height: max(
0px,
calc(var(--drawer-frontmost-height, var(--drawer-height)) - var(--bleed))
);
height: var(--drawer-height, auto);
}
.DrawerPopup[data-nested-drawer-open] {
height: calc(var(--stack-height) + var(--bleed));
overflow: hidden;
}
The data-nested-drawer-open attribute marks drawers behind the frontmost drawer. Use it with data-nested-drawer-swiping to dim or hide parent drawer content while keeping it visible during nested swipe interactions.
.DrawerContent {
transition: opacity 300ms;
}
.DrawerPopup[data-nested-drawer-open] .DrawerContent {
opacity: 0;
}
.DrawerPopup[data-nested-drawer-open][data-nested-drawer-swiping] .DrawerContent {
opacity: 1;
}
The --drawer-swipe-movement-x, --drawer-swipe-movement-y, and --drawer-snap-point-offset CSS variables can be used to create smooth drag and snap offsets:
.DrawerPopup[data-swipe-direction='right'] {
transform: translateX(var(--drawer-swipe-movement-x));
}
.DrawerPopup[data-swipe-direction='down'] {
transform: translateY(
calc(var(--drawer-snap-point-offset) + var(--drawer-swipe-movement-y))
);
}
The data-swipe-direction attribute can be used with data-ending-style to animate directional dismissal:
.DrawerPopup[data-ending-style][data-swipe-direction='right'] {
transform: translateX(100%);
}
.DrawerPopup[data-ending-style][data-swipe-direction='down'] {
transform: translateY(100%);
}
Use --drawer-swipe-progress to fade the backdrop as the drawer is swiped, and --drawer-swipe-strength to scale release transition durations based on swipe velocity.
.DrawerBackdrop {
--backdrop-opacity: 0.2;
opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress)));
}
.DrawerPopup[data-ending-style],
.DrawerBackdrop[data-ending-style] {
transition-duration: calc(var(--drawer-swipe-strength) * 400ms);
}
.DrawerPopup[data-swiping],
.DrawerBackdrop[data-swiping] {
transition-duration: 0ms;
}
API reference
Provider
Provides a shared context for coordinating global Drawer UI, such as indent/background effects based on whether any Drawer is open. Doesn’t render its own HTML element.
childrenJSX.Element—
JSX.ElementProvider.State
type DrawerProviderState = {};IndentBackground
An element placed before <Drawer.Indent> to render a background layer that can be styled based on whether any drawer is open.
Renders a <div> element.
classfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)IndentBackground.State
type DrawerIndentBackgroundState = {
/** Whether any drawer within the nearest <Drawer.Provider> is open. */
active: boolean;
};Indent
A wrapper element intended to contain your app’s main UI.
Applies data-active when any drawer within the nearest <Drawer.Provider> is open.
Renders a <div> element.
classfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Indent.State
type DrawerIndentState = {
/** Whether any drawer within the nearest <Drawer.Provider> is open. */
active: boolean;
};Root
Groups all parts of the drawer. Doesn’t render its own HTML element.
defaultOpenbooleanfalse
open prop instead.booleanopenboolean—
booleanonOpenChangefunction—
((open: boolean, eventDetails: Drawer.Root.ChangeEventDetails) => void)snapPointsDrawerSnapPoint[]—
px/rem units
(for example, '148px' or '30rem').DrawerSnapPoint[]defaultSnapPointUnion—
DrawerSnapPoint | nullsnapPointUnion—
onSnapPointChange to control the snap point.DrawerSnapPoint | nullonSnapPointChangefunction—
((snapPoint: DrawerSnapPoint | null, eventDetails: Drawer.Root.SnapPointChangeEventDetails) => void)actionsRefRefObject<Drawer.Root.Actions | null>—
unmount: Ends the closing phase of the drawer after an externally controlled closing animation finishes.
Call preventUnmountOnClose() in onOpenChange first, otherwise the drawer completes closing on its own.
Whether it leaves the DOM is decided by keepMounted on the portal.close: Closes the drawer imperatively when called.RefObject<Drawer.Root.Actions | null>defaultTriggerIdUnion—
defaultOpen prop to create an initially open drawer.string | nulldisablePointerDismissalbooleanfalse
booleanhandleDrawer.Handle<Payload>—
Drawer.Handle<Payload>modalUniontrue
true: user interaction is limited to just the drawer: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled.false: user interaction with the rest of the document is allowed.'trap-focus': focus is trapped inside the drawer, but document page scroll is not locked and pointer interactions outside of it remain enabled.boolean | 'trap-focus'onOpenChangeCompletefunction—
((open: boolean) => void)snapToSequentialPointsbooleanfalse
booleanswipeDirectionDrawerSwipeDirection'down'
DrawerSwipeDirectiontriggerIdUnion—
open prop to create a controlled drawer.
There’s no need to specify this prop when the drawer is uncontrolled (that is, when the open prop is not set).string | nullchildrenUnion—
JSX.Element | PayloadChildRenderFunction<Payload>Root.State
type DrawerRootState = {};Root.Actions
type DrawerRootActions = { unmount: () => void; close: () => void };Root.ChangeEventReason
type DrawerRootChangeEventReason =
| 'trigger-press'
| 'outside-press'
| 'escape-key'
| 'close-watcher'
| 'close-press'
| 'focus-out'
| 'imperative-action'
| 'swipe'
| 'none';Root.ChangeEventDetails
type DrawerRootChangeEventDetails = (
| { reason: 'trigger-press'; event: KeyboardEvent | MouseEvent | TouchEvent | PointerEvent }
| { reason: 'outside-press'; event: MouseEvent | TouchEvent | PointerEvent }
| { reason: 'escape-key'; event: KeyboardEvent }
| { reason: 'close-press'; event: KeyboardEvent | MouseEvent | PointerEvent }
| { reason: 'focus-out'; event: FocusEvent | KeyboardEvent }
| { reason: 'imperative-action'; event: Event }
| { reason: 'none'; event: Event }
| { reason: 'close-watcher'; event: Event }
| { reason: 'swipe'; event: TouchEvent | PointerEvent }
) & {
/** Cancels Base UI from handling the event. */
cancel: () => void;
/** Allows the event to propagate in cases where Base UI will stop the propagation. */
allowPropagation: () => void;
/** Indicates whether the event has been canceled. */
isCanceled: boolean;
/** Indicates whether the event is allowed to propagate. */
isPropagationAllowed: boolean;
/** The element that triggered the event, if applicable. */
trigger: Element | undefined;
/** Prevents the popup from unmounting until the `unmount` action is called. */
preventUnmountOnClose: () => void;
};Root.SnapPoint
type DrawerRootSnapPoint = number | string;Root.SnapPointChangeEventDetails
type DrawerRootSnapPointChangeEventDetails = (
| { reason: 'trigger-press'; event: KeyboardEvent | MouseEvent | TouchEvent | PointerEvent }
| { reason: 'outside-press'; event: MouseEvent | TouchEvent | PointerEvent }
| { reason: 'escape-key'; event: KeyboardEvent }
| { reason: 'close-press'; event: KeyboardEvent | MouseEvent | PointerEvent }
| { reason: 'focus-out'; event: FocusEvent | KeyboardEvent }
| { reason: 'imperative-action'; event: Event }
| { reason: 'none'; event: Event }
| { reason: 'close-watcher'; event: Event }
| { reason: 'swipe'; event: TouchEvent | PointerEvent }
) & {
/** Cancels Base UI from handling the event. */
cancel: () => void;
/** Allows the event to propagate in cases where Base UI will stop the propagation. */
allowPropagation: () => void;
/** Indicates whether the event has been canceled. */
isCanceled: boolean;
/** Indicates whether the event is allowed to propagate. */
isPropagationAllowed: boolean;
/** The element that triggered the event, if applicable. */
trigger: Element | undefined;
};Root.SnapPointChangeEventReason
type DrawerRootSnapPointChangeEventReason =
| 'trigger-press'
| 'outside-press'
| 'escape-key'
| 'close-watcher'
| 'close-press'
| 'focus-out'
| 'imperative-action'
| 'swipe'
| 'none';Trigger
A button that opens the drawer.
Renders a <button> element.
handleDrawer.Handle<Payload>—
Drawer.Handle<Payload>nativeButtonbooleantrue
<button> element when replacing it
via the render prop.
Set to false if the rendered element is not a button (for example, <div>).booleanpayloadPayload—
Payloadidstring—
triggerId prop).stringclassfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Data attributes
data-popup-open-—
-data-disabled-—
-Attribute | Description | |
|---|---|---|
data-popup-open | Present when the corresponding dialog is open. | |
data-disabled | Present when the trigger is disabled. | |
Trigger.State
type DrawerTriggerState = {
/** Whether the trigger is currently disabled. */
disabled: boolean;
/** Whether the drawer is currently open and was opened by this trigger. */
open: boolean;
};SwipeArea
An invisible area that listens for swipe gestures to open the drawer.
Renders a <div> element.
swipeDirectionDrawerSwipeDirection—
Drawer.Root swipeDirection.DrawerSwipeDirectiondisabledbooleanfalse
booleanclassfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Data attributes
data-open-—
-data-closed-—
-data-disabled-—
-data-swipe-directionUnion—
'up' | 'down' | 'left' | 'right'data-swiping-—
-Attribute | Description | |
|---|---|---|
data-open | Present when the drawer is open. | |
data-closed | Present when the drawer is closed. | |
data-disabled | Present when the swipe area is disabled. | |
data-swipe-direction | Indicates the swipe direction. | |
data-swiping | Present when the drawer is being swiped. | |
SwipeArea.State
type DrawerSwipeAreaState = {
/** Whether the drawer is currently open. */
open: boolean;
/** Whether the swipe area is currently being swiped. */
swiping: boolean;
/** The swipe direction that opens the drawer. */
swipeDirection: SwipeDirection;
/** Whether the swipe area is disabled. */
disabled: boolean;
};VirtualKeyboardProvider
Provides keyboard-aware focus and scroll handling for bottom-sheet drawers with form fields.
childrenJSX.Element—
JSX.ElementVirtualKeyboardProvider.State
type DrawerVirtualKeyboardProviderState = {};Portal
A portal element that moves the popup to a different part of the DOM.
By default, the portal element is appended to <body>.
Renders a <div> element.
containerUnion—
HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | nullclassfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)keepMountedbooleanfalse
booleanrenderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Portal.State
type DrawerPortalState = {};Backdrop
An overlay displayed beneath the popup.
Renders a <div> element.
forceRenderbooleanfalse
booleanclassfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Data attributes
data-open-—
-data-closed-—
-data-starting-style-—
-data-ending-style-—
-Attribute | Description | |
|---|---|---|
data-open | Present when the drawer is open. | |
data-closed | Present when the drawer is closed. | |
data-starting-style | Present when the drawer begins animating in. | |
data-ending-style | Present when the drawer is animating out. | |
CSS variables
--drawer-swipe-progressnumber—
numberCSS Variable | Description | |
|---|---|---|
--drawer-swipe-progress | The swipe progress of the drawer gesture. | |
Backdrop.State
type DrawerBackdropState = {
/** Whether the drawer is currently open. */
open: boolean;
/** The transition status of the component. */
transitionStatus: TransitionStatus;
};Viewport
A positioning container for the drawer popup that can be made scrollable.
Renders a <div> element.
classfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Data attributes
data-open-—
-data-closed-—
-data-nested-—
-data-starting-style-—
-data-ending-style-—
-Attribute | Description | |
|---|---|---|
data-open | Present when the drawer is open. | |
data-closed | Present when the drawer is closed. | |
data-nested | Present when the drawer is nested within another drawer. | |
data-starting-style | Present when the drawer begins animating in. | |
data-ending-style | Present when the drawer is animating out. | |
CSS variables
--drawer-keyboard-insetCSS—
Drawer.VirtualKeyboardProvider.CSSCSS Variable | Description | |
|---|---|---|
--drawer-keyboard-inset | The software keyboard inset, measured from the bottom edge of the layout viewport.
Present only when the drawer is wrapped in Drawer.VirtualKeyboardProvider. | |
Viewport.State
type DrawerViewportState = {
/** Whether the drawer is currently open. */
open: boolean;
/** The transition status of the component. */
transitionStatus: TransitionStatus;
/** Whether the drawer is nested within another drawer. */
nested: boolean;
/** Whether the drawer has nested drawers open. */
nestedDialogOpen: boolean;
};Popup
A container for the drawer contents.
Renders a <div> element.
initialFocusfunction—
false: Do not move focus.true: Move focus based on the default behavior (first tabbable element or popup).RefObject: Move focus to the ref element.function: Called with the interaction type (mouse, touch, pen, or keyboard).
Return an element to focus, true to use the default behavior, or false/undefined to do nothing.boolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)finalFocusfunction—
false: Do not move focus.true: Move focus based on the default behavior (trigger or previously focused element).RefObject: Move focus to the ref element.function: Called with the interaction type (mouse, touch, pen, or keyboard).
Return an element to focus, true to use the default behavior, or false/undefined to do nothing.boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)classfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Data attributes
data-open-—
-data-closed-—
-data-expanded-—
-data-nested-drawer-open-—
-data-nested-drawer-swiping-—
-data-swipe-directionUnion—
'up' | 'down' | 'left' | 'right'data-swipe-dismiss-—
-data-swiping-—
-data-starting-style-—
-data-ending-style-—
-Attribute | Description | |
|---|---|---|
data-open | Present when the drawer is open. | |
data-closed | Present when the drawer is closed. | |
data-expanded | Present when the drawer is at the expanded (full-height) snap point. | |
data-nested-drawer-open | Present when a nested drawer is open. | |
data-nested-drawer-swiping | Present when a nested drawer is being swiped. | |
data-swipe-direction | Indicates the swipe direction. | |
data-swipe-dismiss | Present when the drawer is dismissed by swiping. | |
data-swiping | Present when the drawer is being swiped. | |
data-starting-style | Present when the drawer begins animating in. | |
data-ending-style | Present when the drawer is animating out. | |
CSS variables
--drawer-frontmost-heightCSS—
CSS--drawer-heightCSS—
CSS--drawer-snap-point-offsetCSS—
CSS--drawer-swipe-movement-xCSS—
CSS--drawer-swipe-movement-yCSS—
CSS--drawer-swipe-strengthnumber—
number--nested-drawersnumber—
numberCSS Variable | Description | |
|---|---|---|
--drawer-frontmost-height | The height of the frontmost open drawer in the current nested drawer stack. | |
--drawer-height | The height of the drawer popup. | |
--drawer-snap-point-offset | The snap point offset used for translating the drawer. | |
--drawer-swipe-movement-x | The swipe movement on the X axis. | |
--drawer-swipe-movement-y | The swipe movement on the Y axis. | |
--drawer-swipe-strength | A scalar (0.1-1) used to scale the swipe release transition duration in CSS. | |
--nested-drawers | The number of nested drawers that are currently open. | |
Popup.State
type DrawerPopupState = {
/** Whether the drawer is currently open. */
open: boolean;
/** The transition status of the component. */
transitionStatus: TransitionStatus;
/** Whether the active snap point is the full-height expanded state. */
expanded: boolean;
/** Whether the drawer is nested within a parent drawer. */
nested: boolean;
/** Whether the drawer has nested drawers open. */
nestedDrawerOpen: boolean;
/** Whether a nested drawer is currently being swiped. */
nestedDrawerSwiping: boolean;
/** The swipe direction used to dismiss the drawer. */
swipeDirection: DrawerSwipeDirection;
/** Whether the drawer is being swiped. */
swiping: boolean;
};Content
A container for the drawer contents.
Renders a <div> element.
classfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Content.State
type DrawerContentState = {};Title
A heading that labels the drawer.
Renders an <h2> element.
classfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Title.State
type DrawerTitleState = {};Description
A paragraph with additional information about the drawer.
Renders a <p> element.
classfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Description.State
type DrawerDescriptionState = {};Close
A button that closes the drawer.
Renders a <button> element.
nativeButtonbooleantrue
<button> element when replacing it
via the render prop.
Set to false if the rendered element is not a button (for example, <div>).booleanclassfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Data attributes
data-disabled-—
-Attribute | Description | |
|---|---|---|
data-disabled | Present when the button is disabled. | |
Close.State
type DrawerCloseState = {
/** Whether the button is currently disabled. */
disabled: boolean;
};createHandle
Creates a new handle to connect a Drawer.Root with detached Drawer.Trigger components.
createHandle
type ReturnValue = Drawer.Handle<Payload>;
Handle
Controls a Drawer imperatively and associates detached Drawer.Trigger components with a
Drawer.Root. Create one with Drawer.createHandle() and pass it to the handle prop of the
root and of any triggers rendered outside of it.
The imperative methods take effect only while a root using this handle is mounted; calls made
before a root attaches (or after it unmounts) are ignored.
Handle
function open(triggerId: string | null): void;
Handle
function openWithPayload(payload: Payload): void;
Handle
function close(): void;