# Drawer **Category**: react **URL**: https://ui.darkcode.dev/en/docs/react/components/drawer **Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(overlays)/drawer.mdx > Slide-out panel for supplementary content and actions *** ## Import ```tsx import { Drawer, Button } from "@darkcode-ui/react"; ``` ### Usage ```tsx import {Button, Drawer} from "@darkcode-ui/react"; export function Basic() { return ( Drawer Title

This is a bottom drawer built with React Aria's Modal component. It slides up from the bottom of the screen with a smooth CSS transition.

); } ``` ### Anatomy ```tsx import { Drawer, Button } from "@darkcode-ui/react"; export default () => ( {/* Optional: Drag handle */} {/* Optional: Close button */} ); ``` ### Placement ```tsx import {Button, Drawer} from "@darkcode-ui/react"; export function Placements() { const placements = ["bottom", "top", "left", "right"] as const; return (
{placements.map((placement) => ( {placement === "bottom" && } {placement.charAt(0).toUpperCase() + placement.slice(1)} Drawer

This drawer slides in from the {placement} edge of the screen.

{placement === "top" && }
))}
); } ``` ### Backdrop Variants ```tsx import {Button, Drawer} from "@darkcode-ui/react"; export function BackdropVariants() { const variants = ["opaque", "blur", "transparent"] as const; return (
{variants.map((variant) => ( Backdrop: {variant.charAt(0).toUpperCase() + variant.slice(1)}

This drawer uses the {variant} backdrop variant.

))}
); } ``` ### Snap Points Pass `snapPoints` to `Drawer.Backdrop` to let the drawer rest at intermediate heights. Each value is a fraction of the viewport (`0`–`1`) or a CSS length such as `"320px"` or `"50%"`. Drag the handle to move between snap points, or tap it to cycle through them. The backdrop fades as the drawer is dragged below the `fadeFromIndex` snap point. ```tsx import {Button, Drawer} from "@darkcode-ui/react"; export function SnapPoints() { return ( Snap Points

Drag the handle to snap between 35%, 60%, and{" "} 90% of the viewport height. Tap the handle to cycle through the snap points; tapping at the top closes the drawer.

{Array.from({length: 12}).map((_, i) => (

Item {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque sit amet hendrerit risus.

))}
); } ``` ### Sequential Snap Points Snap points can mix fixed lengths and fractions. Use `defaultActiveSnapPoint` (uncontrolled) or `activeSnapPoint` / `onActiveSnapPointChange` (controlled) to drive the resting position. ```tsx import {Button, Drawer} from "@darkcode-ui/react"; export function SnapPointsSequential() { return ( Sequential Snap Points

Mixes fixed pixel snap points (180px, 420px) with a fractional one (0.9). The drawer opens at the smallest snap point and steps up as you drag or tap the handle.

{Array.from({length: 12}).map((_, i) => (

Item {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.

))}
); } ``` ### Custom Backdrop Fade `fadeFromIndex` controls the snap index from which the backdrop starts fading. Set it to `0` to keep the backdrop fully opaque across every snap point. ```tsx import {Button, Drawer} from "@darkcode-ui/react"; export function SnapPointsCustomFade() { return ( Custom Backdrop Fade

With {"fadeFromIndex={0}"} the backdrop stays fully opaque across every snap point and only fades as the drawer is dragged below the smallest one.

{Array.from({length: 12}).map((_, i) => (

Item {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.

))}
); } ``` ### Handle Only Set `isHandleOnly` on `Drawer.Backdrop` to restrict drag gestures to the `Drawer.Handle`, keeping the header and body fully interactive. ```tsx import {Button, Drawer} from "@darkcode-ui/react"; export function HandleOnly() { return ( Handle-Only

Dragging is restricted to the handle. The header and body no longer start a drag, so text stays selectable and inner controls remain interactive.

{Array.from({length: 12}).map((_, i) => (

Item {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.

))}
); } ``` ### Detached Set `isDetached` on `Drawer.Content` to float the panel away from the viewport edge with rounded corners on every side. ```tsx import {Button, Drawer} from "@darkcode-ui/react"; export function Detached() { const placements = ["bottom", "left", "right"] as const; return (
{placements.map((placement) => ( {placement === "bottom" && } Detached Drawer

The panel floats away from the {placement} edge with rounded corners on every side.

))}
); } ``` ### Non-Dismissable Set `isDismissable={false}` on `Drawer.Backdrop` to prevent closing by clicking outside or dragging. The user must interact with the drawer's action buttons. ```tsx import {Button, Drawer} from "@darkcode-ui/react"; export function NonDismissable() { return ( Confirm Action

This drawer cannot be dismissed by clicking outside or dragging. You must use one of the buttons below.

); } ``` ### Scrollable Content The `Drawer.Body` automatically handles overflow with native scrolling. Drag-to-dismiss is excluded from the body area to avoid scroll conflicts. ```tsx import {Button, Drawer} from "@darkcode-ui/react"; export function ScrollableContent() { return ( Terms & Conditions {Array.from({length: 20}).map((_, i) => (

Paragraph {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet hendrerit risus, sed porttitor quam.

))}
); } ``` ### Controlled State ```tsx "use client"; import {Button, Drawer, useOverlayState} from "@darkcode-ui/react"; import React from "react"; export function Controlled() { const [isOpen, setIsOpen] = React.useState(false); const state = useOverlayState(); return (

With React.useState()

Control the drawer using React's useState hook for simple state management.

Status:{" "} {isOpen ? "open" : "closed"}

Controlled with useState()

This drawer is controlled by React's useState hook. Pass{" "} isOpen and onOpenChange props to manage the drawer state externally.

With useOverlayState()

Use the useOverlayState hook for a cleaner API with convenient methods like open(), close(), and{" "} toggle().

Status:{" "} {state.isOpen ? "open" : "closed"}

Controlled with useOverlayState()

The useOverlayState hook provides dedicated methods for common operations. No need to manually create callbacks—just use{" "} state.open(), state.close(), or{" "} state.toggle().

); } ``` ### With Form ```tsx import {Button, Drawer, Input, Label, TextField} from "@darkcode-ui/react"; export function WithForm() { return ( Edit Profile
); } ``` ### Navigation Drawer ```tsx import type {ComponentType, SVGProps} from "react"; import {Button, Drawer} from "@darkcode-ui/react"; import {Bars, Bell, Envelope, Gear, House, Magnifier, Person} from "@gravity-ui/icons"; export function Navigation() { const navItems: {icon: ComponentType>; label: string}[] = [ {icon: House, label: "Home"}, {icon: Magnifier, label: "Search"}, {icon: Bell, label: "Notifications"}, {icon: Envelope, label: "Messages"}, {icon: Person, label: "Profile"}, {icon: Gear, label: "Settings"}, ]; return ( Navigation ); } ``` ## Related Components - **Modal**: Displays content in a modal overlay - **Button**: Allows a user to perform an action - **CloseButton**: Button for dismissing overlays ## Styling ### Passing Tailwind CSS classes ```tsx import { Drawer, Button } from "@darkcode-ui/react"; function CustomDrawer() { return ( Custom Styled Drawer

This drawer has custom styling applied via Tailwind classes.

); } ``` ### Customizing the component classes To customize the Drawer component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .drawer__backdrop { @apply bg-gradient-to-br from-black/50 to-black/70; } .drawer__dialog { @apply rounded-2xl border border-white/10 shadow-2xl; } .drawer__header { @apply text-center; } .drawer__close-trigger { @apply rounded-full bg-white/10 hover:bg-white/20; } } ``` DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes The Drawer component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/drawer.css)): #### Base Classes - `.drawer__trigger` - Trigger element that opens the drawer - `.drawer__backdrop` - Overlay backdrop behind the drawer - `.drawer__content` - Positioning wrapper for the drawer panel - `.drawer__dialog` - The drawer panel itself - `.drawer__header` - Header section for titles - `.drawer__heading` - Main title text - `.drawer__body` - Main scrollable content area - `.drawer__footer` - Footer section for actions - `.drawer__handle` - Visual drag handle indicator - `.drawer__close-trigger` - Close button element #### Backdrop Variants - `.drawer__backdrop--opaque` - Opaque colored backdrop (default) - `.drawer__backdrop--blur` - Blurred backdrop with glass effect - `.drawer__backdrop--transparent` - Transparent backdrop (no overlay) #### Placement Variants - `.drawer__content--bottom` - Slides up from the bottom edge (default) - `.drawer__content--top` - Slides down from the top edge - `.drawer__content--left` - Slides in from the left edge - `.drawer__content--right` - Slides in from the right edge #### Dialog Variants - `.drawer__dialog--top` - Slides down from the top edge - `.drawer__dialog--bottom` - Slides up from the bottom edge - `.drawer__dialog--left` - Slides in from the left edge - `.drawer__dialog--right` - Slides in from the right edge - `.drawer__dialog--detached` - Floats the panel with rounded corners on all sides #### Detached - `.drawer__content--detached` - Insets the panel from the viewport edges ### CSS Variables - `--drawer-backdrop-opacity` - JS-driven backdrop tint opacity for snap-point fade (default `1`) ### Interactive States The component supports these interactive states: - **Focus**: `:focus-visible` or `[data-focus-visible="true"]` - Applied to trigger and close button - **Hover**: `:hover` or `[data-hovered="true"]` - Applied to close button on hover - **Active**: `:active` or `[data-pressed="true"]` - Applied to trigger and close button when pressed - **Entering**: `[data-entering]` - Applied during drawer opening animation - **Exiting**: `[data-exiting]` - Applied during drawer closing animation - **Placement**: `[data-placement="*"]` - Applied based on drawer position (top, bottom, left, right) - **Dragging**: `.drawer-dragging` - Applied to the dialog while a pointer drag is active - **Snap points**: `[data-snap-points="true"]` - Applied to the dialog when snap points are enabled - **Detached**: `[data-detached="true"]` - Applied to the content when `isDetached` is set ## API Reference ### Drawer | Prop | Type | Default | Description | | ---------- | ---------------------- | ------- | ------------------------------ | | `children` | `ReactNode` | - | Trigger and backdrop elements | | `state` | `UseOverlayStateReturn` | - | Controlled overlay state | ### Drawer.Trigger | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ---------------------- | | `children` | `ReactNode` | - | Custom trigger content | | `className` | `string` | - | CSS classes | ### Drawer.Backdrop | Prop | Type | Default | Description | | --------------------------- | ------------------------------------- | ---------- | ------------------------- | | `variant` | `"opaque" \| "blur" \| "transparent"` | `"opaque"` | Backdrop overlay style | | `isDismissable` | `boolean` | `true` | Close on backdrop click or drag past threshold | | `isHandleOnly` | `boolean` | `false` | Restrict drag to `Drawer.Handle` only | | `closeThreshold` | `number` | `0.25` | Fraction of visible size dragged before closing | | `shouldAutoFocus` | `boolean` | `false` | Auto-focus first focusable element on open | | `snapPoints` | `(number \| string)[]` | - | Snap point heights (fraction `0`–`1` or CSS length) | | `activeSnapPoint` | `number \| string \| null` | - | Controlled active snap point | | `defaultActiveSnapPoint` | `number \| string \| null` | - | Initial active snap point (uncontrolled) | | `onActiveSnapPointChange` | `(snapPoint) => void` | - | Active snap point change handler | | `fadeFromIndex` | `number` | last index | Snap index from which the backdrop fades | | `onDrag` | `(event, percentageDragged) => void` | - | Called continuously while dragging | | `onRelease` | `(event, open) => void` | - | Called when a drag ends | | `onClose` | `() => void` | - | Called when the drawer closes | | `onAnimationEnd` | `(open: boolean) => void` | - | Called after the open/close animation | | `isKeyboardDismissDisabled` | `boolean` | `false` | Disable ESC key to close | | `isOpen` | `boolean` | - | Controlled open state | | `onOpenChange` | `(isOpen: boolean) => void` | - | Open state change handler | | `className` | `string \| (values) => string` | - | Backdrop CSS classes | ### Drawer.Content | Prop | Type | Default | Description | | ------------ | --------------------------------------------- | ---------- | ------------------------- | | `placement` | `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | Edge the drawer slides from | | `isDetached` | `boolean` | `false` | Float the panel with rounded corners on all sides | | `className` | `string \| (values) => string` | - | Content CSS classes | ### Drawer.Dialog | Prop | Type | Default | Description | | ------------------ | ------------------------------ | ---------- | -------------------------- | | `children` | `ReactNode` | - | Dialog content | | `className` | `string` | - | CSS classes | | `role` | `string` | `"dialog"` | ARIA role | | `aria-label` | `string` | - | Accessibility label | | `aria-labelledby` | `string` | - | ID of label element | ### Drawer.Header | Prop | Type | Default | Description | | ----------- | ----------- | ------- | -------------- | | `children` | `ReactNode` | - | Header content | | `className` | `string` | - | CSS classes | ### Drawer.Heading | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ------------ | | `children` | `ReactNode` | - | Title text | | `className` | `string` | - | CSS classes | ### Drawer.Body | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ------------ | | `children` | `ReactNode` | - | Body content | | `className` | `string` | - | CSS classes | ### Drawer.Footer | Prop | Type | Default | Description | | ----------- | ----------- | ------- | -------------- | | `children` | `ReactNode` | - | Footer content | | `className` | `string` | - | CSS classes | ### Drawer.Handle | Prop | Type | Default | Description | | -------------- | --------- | ------- | ---------------------------------------------------- | | `preventCycle` | `boolean` | `false` | Disable cycling snap points (or closing) on click | | `className` | `string` | - | CSS classes | ### Drawer.CloseTrigger | Prop | Type | Default | Description | | ----------- | ------------------------------ | ------- | ------------------- | | `children` | `ReactNode` | - | Custom close button | | `className` | `string \| (values) => string` | - | CSS classes | ### useOverlayState Hook ```tsx import { useOverlayState } from "@darkcode-ui/react"; const state = useOverlayState({ defaultOpen: false, onOpenChange: (isOpen) => console.log(isOpen), }); state.isOpen; // Current state state.open(); // Open drawer state.close(); // Close drawer state.toggle(); // Toggle state state.setOpen(); // Set state directly ``` ## Accessibility Implements [WAI-ARIA Dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/): - **Focus trap**: Focus locked within drawer when open - **Keyboard**: `ESC` closes (when dismissable), `Tab` cycles elements - **Screen readers**: Proper ARIA attributes via React Aria - **Scroll lock**: Body scroll disabled when open - **Drag to dismiss**: Supports pointer-based drag gestures on handle, header, and footer areas