27.7k

Drawer

Slide-out panel for supplementary content and actions

Import

import { Drawer, Button } from "@darkcode-ui/react";

Usage

import {Button, Drawer} from "@darkcode-ui/react";

export function Basic() {
  return (
    <Drawer>

Anatomy

import { Drawer, Button } from "@darkcode-ui/react";

export default () => (
  <Drawer>
    <Button>Open Drawer</Button>
    <Drawer.Backdrop>
      <Drawer.Content>
        <Drawer.Dialog>
          <Drawer.Handle /> {/* Optional: Drag handle */}
          <Drawer.CloseTrigger /> {/* Optional: Close button */}
          <Drawer.Header>
            <Drawer.Heading />
          </Drawer.Header>
          <Drawer.Body />
          <Drawer.Footer />
        </Drawer.Dialog>
      </Drawer.Content>
    </Drawer.Backdrop>
  </Drawer>
);

Placement

import {Button, Drawer} from "@darkcode-ui/react";

export function Placements() {
  const placements = ["bottom", "top", "left", "right"] as const;

Backdrop Variants

import {Button, Drawer} from "@darkcode-ui/react";

export function BackdropVariants() {
  const variants = ["opaque", "blur", "transparent"] as const;

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.

import {Button, Drawer} from "@darkcode-ui/react";

export function SnapPoints() {
  return (
    <Drawer>

Sequential Snap Points

Snap points can mix fixed lengths and fractions. Use defaultActiveSnapPoint (uncontrolled) or activeSnapPoint / onActiveSnapPointChange (controlled) to drive the resting position.

import {Button, Drawer} from "@darkcode-ui/react";

export function SnapPointsSequential() {
  return (
    <Drawer>

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.

import {Button, Drawer} from "@darkcode-ui/react";

export function SnapPointsCustomFade() {
  return (
    <Drawer>

Handle Only

Set isHandleOnly on Drawer.Backdrop to restrict drag gestures to the Drawer.Handle, keeping the header and body fully interactive.

import {Button, Drawer} from "@darkcode-ui/react";

export function HandleOnly() {
  return (
    <Drawer>

Detached

Set isDetached on Drawer.Content to float the panel away from the viewport edge with rounded corners on every side.

import {Button, Drawer} from "@darkcode-ui/react";

export function Detached() {
  const placements = ["bottom", "left", "right"] as const;

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.

import {Button, Drawer} from "@darkcode-ui/react";

export function NonDismissable() {
  return (
    <Drawer>

Scrollable Content

The Drawer.Body automatically handles overflow with native scrolling. Drag-to-dismiss is excluded from the body area to avoid scroll conflicts.

import {Button, Drawer} from "@darkcode-ui/react";

export function ScrollableContent() {
  return (
    <Drawer>

Controlled State

"use client";

import {Button, Drawer, useOverlayState} from "@darkcode-ui/react";
import React from "react";

With Form

import {Button, Drawer, Input, Label, TextField} from "@darkcode-ui/react";

export function WithForm() {
  return (
    <Drawer>
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";

Styling

Passing Tailwind CSS classes

import { Drawer, Button } from "@darkcode-ui/react";

function CustomDrawer() {
  return (
    <Drawer>
      <Button>Open Drawer</Button>
      <Drawer.Backdrop className="bg-black/80">
        <Drawer.Content>
          <Drawer.Dialog className="bg-linear-to-br from-purple-500 to-pink-500 text-white">
            <Drawer.CloseTrigger />
            <Drawer.Header>
              <Drawer.Heading>Custom Styled Drawer</Drawer.Heading>
            </Drawer.Header>
            <Drawer.Body>
              <p>This drawer has custom styling applied via Tailwind classes.</p>
            </Drawer.Body>
            <Drawer.Footer>
              <Button slot="close">Close</Button>
            </Drawer.Footer>
          </Drawer.Dialog>
        </Drawer.Content>
      </Drawer.Backdrop>
    </Drawer>
  );
}

Customizing the component classes

To customize the Drawer component classes, you can use the @layer components directive.


Learn more.

@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 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):

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

PropTypeDefaultDescription
childrenReactNode-Trigger and backdrop elements
stateUseOverlayStateReturn-Controlled overlay state

Drawer.Trigger

PropTypeDefaultDescription
childrenReactNode-Custom trigger content
classNamestring-CSS classes

Drawer.Backdrop

PropTypeDefaultDescription
variant"opaque" | "blur" | "transparent""opaque"Backdrop overlay style
isDismissablebooleantrueClose on backdrop click or drag past threshold
isHandleOnlybooleanfalseRestrict drag to Drawer.Handle only
closeThresholdnumber0.25Fraction of visible size dragged before closing
shouldAutoFocusbooleanfalseAuto-focus first focusable element on open
snapPoints(number | string)[]-Snap point heights (fraction 0–1 or CSS length)
activeSnapPointnumber | string | null-Controlled active snap point
defaultActiveSnapPointnumber | string | null-Initial active snap point (uncontrolled)
onActiveSnapPointChange(snapPoint) => void-Active snap point change handler
fadeFromIndexnumberlast indexSnap 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
isKeyboardDismissDisabledbooleanfalseDisable ESC key to close
isOpenboolean-Controlled open state
onOpenChange(isOpen: boolean) => void-Open state change handler
classNamestring | (values) => string-Backdrop CSS classes

Drawer.Content

PropTypeDefaultDescription
placement"top" | "bottom" | "left" | "right""bottom"Edge the drawer slides from
isDetachedbooleanfalseFloat the panel with rounded corners on all sides
classNamestring | (values) => string-Content CSS classes

Drawer.Dialog

PropTypeDefaultDescription
childrenReactNode-Dialog content
classNamestring-CSS classes
rolestring"dialog"ARIA role
aria-labelstring-Accessibility label
aria-labelledbystring-ID of label element

Drawer.Header

PropTypeDefaultDescription
childrenReactNode-Header content
classNamestring-CSS classes

Drawer.Heading

PropTypeDefaultDescription
childrenReactNode-Title text
classNamestring-CSS classes

Drawer.Body

PropTypeDefaultDescription
childrenReactNode-Body content
classNamestring-CSS classes

Drawer.Footer

PropTypeDefaultDescription
childrenReactNode-Footer content
classNamestring-CSS classes

Drawer.Handle

PropTypeDefaultDescription
preventCyclebooleanfalseDisable cycling snap points (or closing) on click
classNamestring-CSS classes

Drawer.CloseTrigger

PropTypeDefaultDescription
childrenReactNode-Custom close button
classNamestring | (values) => string-CSS classes

useOverlayState Hook

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:

  • 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

On this page