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
- <dialog>
- <dialog closedby> — only the closedBy prop depends on it: without it, Escape closes and a backdrop click does not
- :has()
- :modal
- ::backdrop
- Oklab and OkLCh — comes from the design tokens
- @starting-style — only the open and close animation depends on it
- Web animations
- Pointer Events
- backdrop-filter — covered by a -webkit- fallback, not counted in the minimum
- Logical properties
- :dir() — only right-to-left layouts depend on it
- :focus-visible
- :where()
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
DrawerPropsDrawerTriggerPropsDrawerContentPropsDrawerCloseProps
Minimal Composition
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 providerDrawerTrigger: open actionDrawerContent: edge-mounted modal panel
Optional Parts
DrawerClose: closes the drawer when clicked; wraps your own button withasChild
Your Own Markup
- a heading whose
idis passed toDrawerContentasaria-labelledby(oraria-labelonDrawerContent); the drawer must have a name - action buttons that answer the drawer (Cancel, Confirm) inside a
<form method="dialog">
Composition Order
Typical composition:
DrawerDrawerTriggerDrawerContentwith your heading, an optionalDrawerClose, 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 (
overlayonDrawerContent); 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 rowDrawerTitle→ aHeadingwith anid, referenced byaria-labelledbyonDrawerContentDrawerCloseButton→DrawerClose asChildaround your own button, for example anIconButtonwith anaria-labeluseDrawerContext()no longer returnscontentIdortitleId, andDrawerTriggerno longer setsaria-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
DrawerContentwithoutaria-labelledbyoraria-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.
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.
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.
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.
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.
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.
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.
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.
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.
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; withasChildit renders your own button instead - Calls
onChangeOpen(false)before youronClick, 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
DrawerDrawerTriggerDrawerContent- Your heading, an optional
DrawerClose, body, and a<form method="dialog">with action buttons
Valid Composition Patterns
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.