Files
skills/docs/ui/react/react-ui/src/components/drawer/drawer.md
2026-05-29 08:20:56 +01:00

6.8 KiB

Drawer

(no submodules)

- drawer.variants — `drawerOverlayVariants`, `drawerC…
  • drawer.variants — drawerOverlayVariants, drawerContentVariants, drawerHandleVariants, drawerHeaderVariants, `drawerFooterVariants…

  • drawer.variants — drawerOverlayVariants, `drawerC…

  • drawer.variants — drawerOverlayVariants, drawerContentVariants, drawerHandleVariants, drawerHeaderVariants, `drawerFooterVariants…

  • drawer.variants — drawerOverlayVariants, `drawerC…

  • drawer.variants — drawerOverlayVariants, drawerContentVariants, drawerHandleVariants, drawerHeaderVariants, `drawerFooterVariants…

  • drawer.variants — drawerOverlayVariants, `drawerC…

  • drawer.variants — drawerOverlayVariants, drawerContentVariants, drawerHandleVariants, drawerHeaderVariants, `drawerFooterVariants…

  • drawer.variants — drawerOverlayVariants, drawerContentVariants, drawerHandleVariants, drawerHeaderVariants, `drawerFooterVariants…

Purpose

<Drawer> and its sub-components (<DrawerTrigger>, <DrawerPortal>, <DrawerOverlay>, <DrawerContent>, <DrawerHandle>, <DrawerHeader>, <DrawerFooter>, <DrawerTitle>, <DrawerDescription>, <DrawerClose>) are the chassis bottom-anchored modal panel primitives built on vaul. Vaul wraps @radix-ui/react-dialog internally so the WAI-ARIA dialog semantics (focus-trap, restore-focus, Escape closes, click-outside closes) are inherited; vaul layers on the swipe-down dismiss gesture, snap-points, and background-scale animation that distinguish a Drawer from a Sheet.

Per the WAI-ARIA dialog pattern, every drawer MUST contain a <DrawerTitle> so the accessible name is announced. Use className="sr-only" if the visible UI does not need a heading. Drawer is mobile-first — the panel slides up from the bottom and is grabbable by the small handle at the top.

Features

  • Bottom-anchored slide-up panel with swipe-down dismiss gesture (vaul-native).
  • Snap-points support via vaul (snapPoints={[0.4, 0.8]}).
  • Background-scale animation while the drawer is open (shouldScaleBackground, default true).
  • Drag handle at the top of the panel (rendered automatically inside <DrawerContent>).
  • Focus-trap on open; restore-focus on close (vaul → Radix-managed).
  • Escape closes the drawer; click on the overlay backdrop closes the drawer.
  • All sub-components forward refs.
  • Theme-driven cva overrides for every slot via useThemedVariants("Drawer<Slot>").

Usage: Basic Example

import {
    Drawer,
    DrawerClose,
    DrawerContent,
    DrawerDescription,
    DrawerFooter,
    DrawerHeader,
    DrawerTitle,
    DrawerTrigger,
} from "@suaveplan/react-ui";

export function AccountDrawer() {
    return (
        <Drawer>
            <DrawerTrigger>Open settings</DrawerTrigger>
            <DrawerContent>
                <DrawerHeader>
                    <DrawerTitle>Account settings</DrawerTitle>
                    <DrawerDescription>
                        Manage your account preferences.
                    </DrawerDescription>
                </DrawerHeader>
                <DrawerFooter>
                    <DrawerClose>Close</DrawerClose>
                </DrawerFooter>
            </DrawerContent>
        </Drawer>
    );
}

Usage: Advanced Example

import {
    Button,
    Drawer,
    DrawerContent,
    DrawerDescription,
    DrawerFooter,
    DrawerHeader,
    DrawerTitle,
} from "@suaveplan/react-ui";
import { useState } from "react";

export function SnappingFiltersDrawer() {
    const [open, setOpen] = useState(false);
    return (
        <Drawer
            open={open}
            onOpenChange={setOpen}
            snapPoints={[0.4, 0.7, 1.0]}
            shouldScaleBackground
        >
            <Button onClick={() => setOpen(true)}>Open filters</Button>
            <DrawerContent>
                <DrawerHeader>
                    <DrawerTitle>Filters</DrawerTitle>
                    <DrawerDescription>
                        Drag the handle to expand.
                    </DrawerDescription>
                </DrawerHeader>
                <div className="p-4">…filter controls…</div>
                <DrawerFooter>
                    <Button variant="outline" onClick={() => setOpen(false)}>
                        Cancel
                    </Button>
                    <Button onClick={() => setOpen(false)}>Apply</Button>
                </DrawerFooter>
            </DrawerContent>
        </Drawer>
    );
}

API Reference

<Drawer>

Wraps vaul's Drawer.Root. Inherits every prop from vaul including:

Prop Type Default Description
open boolean uncontrolled Controlled open state.
defaultOpen boolean false Initial open state.
onOpenChange (open: boolean) => void none Fires on open/close transitions.
snapPoints (number | string)[] none Optional snap-point heights.
shouldScaleBackground boolean true Scale the page behind the drawer while open.
dismissible boolean true Allow Escape / click-outside / drag-down dismissal.
modal boolean true Keep focus trapped inside the drawer.

<DrawerContent>

Renders the bottom-anchored panel. Self-portals via <DrawerPortal> and renders a drag handle at the top.

<DrawerTitle>

REQUIRED — vaul/Radix surfaces a dev warning when omitted. Render className="sr-only" if no visible heading is needed.

<DrawerTrigger>, <DrawerPortal>, <DrawerOverlay>, <DrawerHandle>, <DrawerHeader>, <DrawerFooter>, <DrawerDescription>, <DrawerClose>

Direct re-exports of the vaul sub-components with the chassis cva applied.

Implementation Notes

  • The chassis NEVER assigns a numeric z-index to the overlay or content panels — vaul/Radix portals handle stacking.
  • <DrawerContent> self-portals via <DrawerPortal> and renders a <DrawerHandle> at the top automatically — consumers do not need to compose them manually. This matches the shadcn drawer upstream recipe.
  • Drag-down dismissal is a vaul-native gesture that requires real pointer events; happy-dom-based unit tests cover the open/close lifecycle via Escape and onOpenChange rather than the drag gesture itself. End-to-end coverage of the swipe gesture lives in the consumer's Playwright suite.
  • shouldScaleBackground defaults to true (chassis-overrides vaul's false) because the scale-down effect is the primary visual cue distinguishing a Drawer from a Sheet.
  • Vaul's DrawerClose button forwards to onOpenChange(false) rather than mutating internal state directly — controlled consumers should listen on onOpenChange and update their local open state to honour the close request.