Skip to content
v2.5.0
English
Русский
Documentation

Drawer

Drawer is a compound component for edge-mounted overlays that preserve more page context than a centered modal.
drawer.tsx · 27 lines
import {
  Button,
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerTrigger,
  Heading,
  Icon,
  IconButton,
  Text,
} from '@vega-ui/react';
import { XIcon } from '@vega-ui/icons';
Browser support
Widely available
  • Chrome 111
  • Edge 111
  • Firefox 121
  • Safari 15.6
  • Safari on iOS 15.6
Bundle size
Tested in
48 tests

Summary

Drawer is a compound component for edge-mounted overlays that preserve more page context than a centered modal. DrawerContent renders a native <dialog> opened with showModal(), so the browser puts it in the top layer, makes the rest of the page inert, and draws the backdrop. It is the side-panel member of the overlay family and usually fits secondary editing, filters, and contextual workspace flows. DrawerClose turns your own button into a close button; headings and layout are plain markup you compose yourself.

Imports

import {
  Drawer,
  DrawerTrigger,
  DrawerContent,
  DrawerClose,
  type DrawerProps,
} from '@vega-ui/react';

Exported Types

  • DrawerProps
  • DrawerTriggerProps
  • DrawerContentProps
  • DrawerCloseProps

Minimal Composition

minimal-composition.tsx · 43 lines
import { useId, type CSSProperties } from 'react';
import {
  Button,
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerTrigger,
  Heading,
  Icon,
  IconButton,
  Text,
} from '@vega-ui/react';

Required Parts

  • Drawer: root state and context provider
  • DrawerTrigger: open action
  • DrawerContent: edge-mounted modal panel

Optional Parts

  • DrawerClose: closes the drawer when clicked; wraps your own button with asChild

Your Own Markup

  • a heading whose id is passed to DrawerContent as aria-labelledby (or aria-label on DrawerContent); the drawer must have a name
  • action buttons that answer the drawer (Cancel, Confirm) inside a <form method="dialog">

Composition Order

Typical composition:

  1. Drawer
  2. DrawerTrigger
  3. DrawerContent with your heading, an optional DrawerClose, body, and a <form method="dialog"> for action buttons

Variants

  • Position is the primary source-level variant for drawers.
  • Common product variants are navigation drawer, filter drawer, and detail drawer.
  • Dimmed and transparent backdrops (overlay on DrawerContent); the drawer stays modal either way.

Migrating From 2.x

DrawerHeader, DrawerTitle, and DrawerCloseButton were removed: the library keeps only parts that carry behavior plain markup cannot express. DrawerClose is the behavior-only replacement for the close button.

  • DrawerHeader → your own layout element, for example a flex row
  • DrawerTitle → a Heading with an id, referenced by aria-labelledby on DrawerContent
  • DrawerCloseButton → DrawerClose asChild around your own button, for example an IconButton with an aria-label
  • useDrawerContext() no longer returns contentId or titleId, and DrawerTrigger no longer sets aria-controls

Edge Cases

  • Confirm position-specific layout and animation.
  • Narrow viewports need extra testing for content overflow.
  • Use a drawer only when preserving page context matters more than full modal isolation.
  • If the content becomes a long multi-step flow, a routed page or dedicated screen may be clearer than an ever-growing drawer.

Common Mistakes

  • Leaving DrawerContent without aria-labelledby or aria-label.
  • Using a drawer when a dialog would be simpler and more focused.
  • Letting drawer content become a full page inside a cramped side panel.
  • Letting action buttons scroll out of view in long drawer content.

Examples

Basic

Basic: order inspector

A drawer that shows details of one record next to the page. The heading names the drawer through aria-labelledby, and the round close button sits in the top end corner; a backdrop click, Escape or a touch swipe toward the edge also closes it.

side-inspector.tsx · 60 lines
import { useId, type CSSProperties } from 'react';
import {
  Badge,
  Button,
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerTrigger,
  Heading,
  Icon,
  IconButton,
  Text,

Controlled/Stateful

Controlled/Stateful: one drawer for a list with previous and next

The parent owns open and the selected ticket, so any row opens the same drawer and the arrows inside it move through the list without closing it. aria-current marks the row that is shown.

controlled-open-state.tsx · 90 lines
import { useId, useState, type CSSProperties } from 'react';
import {
  Button,
  Drawer,
  DrawerClose,
  DrawerContent,
  Heading,
  Icon,
  IconButton,
  Text,
} from '@vega-ui/react';
import { ChevronDownIcon, ChevronUpIcon, XIcon } from '@vega-ui/icons';

Form/Integration

Form/Integration: invite form with native validation

A <form method="dialog"> with type="email" multiple required: the browser validates the addresses, and a valid submit reads them with FormData in onSubmit and closes the drawer. The page shows the pending invites below the trigger.

short-form-flow.tsx · 89 lines
import { useId, useState, type CSSProperties, type FormEvent } from 'react';
import {
  Button,
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerTrigger,
  Heading,
  HelperText,
  Icon,
  IconButton,
  Label,

Layout/Overlay

Layout/Overlay: long scrollable list

With fullHeight the drawer takes the full height of the viewport, and only the list scrolls: it takes the remaining space with flex: 1 and min-height: 0, and overscroll-behavior: contain keeps its scroll from reaching the page.

activity-panel.tsx · 72 lines
import { useId, type CSSProperties } from 'react';
import {
  Button,
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerTrigger,
  Heading,
  Icon,
  IconButton,
  Text,
} from '@vega-ui/react';

Layout/Overlay: drawer without a backdrop

overlay={false} and blurred={false} leave the page visible behind an inspector pinned to the top end corner with position='end-start'. The drawer is still modal: the page behind it is inert until it closes.

drawer-without-overlay.tsx · 55 lines
import { useId, type CSSProperties } from 'react';
import {
  Button,
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerTrigger,
  Heading,
  Icon,
  IconButton,
  Text,
} from '@vega-ui/react';

Error

Error: invalid IBAN in a drawer form

The check runs on submit, so the form uses noValidate and a controlled drawer: an invalid IBAN keeps the drawer open and shows an error linked with aria-describedby and aria-invalid, a valid one saves and closes it. Change a digit of the prefilled IBAN to see the error.

form-error.tsx · 121 lines
import { useId, useState, type CSSProperties, type FormEvent } from 'react';
import {
  Button,
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerTrigger,
  Heading,
  HelperText,
  Icon,
  IconButton,
  Label,

Disabled

Disabled: read-only settings for non-admins

<Fieldset disabled> disables every field and the Save button inside it at once, and aria-describedby on the fieldset explains why. Toggle the checkbox to see the same drawer for an admin.

read-only-settings.tsx · 81 lines
import { useId, useState, type CSSProperties } from 'react';
import {
  Button,
  Checkbox,
  CheckboxCheckedIcon,
  CheckboxIndeterminateIcon,
  CheckboxIndicator,
  CheckboxInput,
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerTrigger,

Edge

Edge: drawer inside a drawer

The address form opens in a second drawer over the order. Escape and the backdrop close only the top drawer, and saving the address returns you to the order with the new value.

nested-drawers.tsx · 93 lines
import { useId, useState, type CSSProperties, type FormEvent } from 'react';
import {
  Button,
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerTrigger,
  Heading,
  Icon,
  IconButton,
  Label,
  Text,

Edge: start side in right-to-left text

position='start' follows the writing direction of DrawerContent, like CSS logical properties. The drawer renders into document.body, so it takes dir from there or from its own dir prop: here the button switches it to Arabic and the drawer opens from the right.

drawer-from-the-start-side.tsx · 66 lines
import { useId, useState, type CSSProperties } from 'react';
import {
  Button,
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerTrigger,
  Heading,
  Icon,
  IconButton,
} from '@vega-ui/react';
import { XIcon } from '@vega-ui/icons';

Anatomy

Overview

Drawer owns open state, position, and dismissal rules across its exported parts. DrawerContent is the native modal <dialog> that carries focus, the backdrop, and transitions.

Required Parts

Drawer

Root provider for drawer state.

DrawerTrigger

Open action for the drawer.

DrawerContent

Main edge-mounted panel.

  • Owns: the native modal <dialog>, its ::backdrop, and open and close transitions
  • Renders into document.body, so no portal part is needed

Optional Parts

DrawerClose

Closes the drawer when clicked.

  • Required: no
  • Renders a <button type="button"> with no styles, icon, or label; with asChild it renders your own button instead
  • Calls onChangeOpen(false) before your onClick, so the close transition plays and a controlled parent can keep the drawer open
  • Give an icon-only close button an aria-label

Your Own Markup

The library does not ship header or title parts. They carry no behavior that plain markup cannot express, so you compose them yourself.

Heading

Names the drawer. A name is required: reference a heading's id with aria-labelledby on DrawerContent, or set aria-label on DrawerContent.

Action buttons

Buttons that answer the drawer go in a <form method="dialog">: submitting it closes the drawer through onChangeOpen(false), and the submit button's value becomes the dialog's returnValue. Use DrawerClose for a standalone close button.

Composition Order

  1. Drawer
  2. DrawerTrigger
  3. DrawerContent
  4. Your heading, an optional DrawerClose, body, and a <form method="dialog"> with action buttons

Valid Composition Patterns

minimal-composition.tsx · 43 lines
import { useId, type CSSProperties } from 'react';
import {
  Button,
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerTrigger,
  Heading,
  Icon,
  IconButton,
  Text,
} from '@vega-ui/react';

Invalid Composition Patterns

DrawerContent outside Drawer

Breaks root context and overlay behavior.

Content without a name

DrawerContent does not name itself. Without aria-labelledby or aria-label, screen readers announce an unnamed dialog.

Long drawer content without a scroll strategy

Causes actions or important content to disappear off screen.

Using a drawer for a fully modal confirm flow

This usually indicates Dialog is the better fit.