import {
Button,
Heading,
Icon,
IconButton,
Sheet,
SheetClose,
SheetContent,
SheetHandle,
SheetTrigger,
Text,
} from '@vega-ui/react';- Browser support
- Newly available 2024
- Chrome 111
- Edge 111
- Firefox 128
- Safari 16.4
- Safari on iOS 16.4
- 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
- Registered custom properties
- @starting-style — only the open and close animation depends on it
- Resize observer
- Visual viewport API
- Web animations
- Pointer Events
- backdrop-filter — covered by a -webkit- fallback, not counted in the minimum
- clip-path
- Individual transform properties
- :focus-visible
- :where()
Summary
Sheet is a compound component for panel-style overlays, usually bottom-mounted or mobile-first, with trigger, content, and handle parts. SheetClose turns your own button into a close button; headings and layout are plain markup you compose yourself. SheetContent 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 touch- and panel-oriented overlay in the family, especially when snap points or bottom-entry behavior matter.
Imports
import {
Sheet,
SheetTrigger,
SheetContent,
SheetClose,
SheetHandle,
type SheetProps,
} from '@vega-ui/react';Exported Types
SheetPropsSheetTriggerPropsSheetHandlePropsSheetContentPropsSheetCloseProps
Minimal Composition
import { useId, type CSSProperties } from 'react';
import {
Button,
Heading,
Icon,
IconButton,
Sheet,
SheetClose,
SheetContent,
SheetHandle,
SheetTrigger,
Text,Required Parts
Sheet: root state and context providerSheetTrigger: open action surfaceSheetContent: modal sheet surface
Optional Parts
SheetHandle: drag or gesture affordanceSheetClose: closes the sheet when clicked; wraps your own button withasChild
Your Own Markup
- a heading whose
idis passed toSheetContentasaria-labelledby(oraria-labelonSheetContent); the sheet must have a name - action buttons that answer the sheet inside a
<form method="dialog"> - a scrollable region: an element with
overflow: auto
Composition Order
SheetSheetTriggerSheetContentSheetHandle- Your heading, an optional
SheetClose, and body
Variants
- Bottom-sheet layouts versus general panel layouts
- Trigger-driven versus controlled open state
- Snap-point based sheets versus fully open sheets
closedByonSheetContent: closed by any action, byEscapeonly, or not by the user at all- Dimmed versus transparent backdrop (
overlayonSheetContent); the sheet stays modal either way - Mobile-first presentation versus broader cross-device panel usage
Migrating From 2.x
SheetHeader and SheetMain were removed: the library keeps only parts that carry behavior plain markup cannot express. SheetContent now has the padding they used to provide.
SheetHeader→ aHeadingwith anid, referenced byaria-labelledbyonSheetContentSheetMain→ plain content, or an element withoverflow: autofor a scrollable region- New:
SheetClose, a close part with no styles of its own (Sheet had no close button before) useSheetContext()no longer returnscontentId, andSheetTriggerno longer setsaria-controls
Edge Cases
- Validate gesture affordances on touch devices.
- Snap points should be tested against real content height.
- Nested overlays need focus restoration and stacking checks.
- Non-closable sheets need especially clear exit paths.
Common Mistakes
- Leaving
SheetContentwithoutaria-labelledbyoraria-label. - Treating a sheet like a desktop modal dialog.
- Using snap points without checking content usability at each stop.
- Forgetting to test gesture, focus, and dismissal together.
Examples
Basic
Basic: share sheet with heading and close button
The sheet fits its content and closes with the round button, a swipe down, a backdrop click or Escape. The heading names it through aria-labelledby, the address describes it through aria-describedby, and the copy result is announced through a role="status" line.
import { useId, useState, type CSSProperties } from 'react';
import {
Button,
Heading,
Icon,
IconButton,
Sheet,
SheetClose,
SheetContent,
SheetHandle,
SheetTrigger,
Text,Controlled/Stateful
Controlled/Stateful: snap point in your state
With activeSnapPoint and onChangeActiveSnapPoint the snap point lives in your state, so the Peek, Half and Full buttons move the sheet and a drag updates the pressed button. height: 100% makes the sheet as tall as the viewport, so all three snap points apply.
import { useId, useState } from 'react';
import {
Button,
Heading,
Sheet,
SheetContent,
SheetHandle,
SheetTrigger,
Text,
} from '@vega-ui/react';
const snapPoints: Array<`${number}px` | number> = ['180px', 0.5, 1];Controlled/Stateful: picker that closes on choice
The sheet's open is controlled, so choosing a delivery slot stores it and closes the sheet in one handler. The trigger shows the current choice, and aria-pressed marks it inside the sheet.
import { useId, useState, type CSSProperties } from 'react';
import {
Button,
Heading,
Icon,
IconButton,
Sheet,
SheetClose,
SheetContent,
SheetHandle,
SheetTrigger,
Text,Form/Integration
Form/Integration: feedback form
A <form method="dialog"> with a number field limited by min, max and step and an optional note. The browser validates the tip, a valid submit is read with FormData and closes the sheet; on phones the sheet rises above the on-screen keyboard.
import { useId, useState, type CSSProperties, type FormEvent } from 'react';
import {
Button,
Heading,
Icon,
IconButton,
Label,
Sheet,
SheetClose,
SheetContent,
SheetHandle,
SheetTrigger,Layout/Overlay
Layout/Overlay: long list with snap points
Forty places make the sheet as tall as the viewport, so it rests at 160 px, half and full height. The list is the scrollable element with overscroll-behavior: contain; it scrolls once the sheet reaches the top, and dragging down at its top moves the sheet.
import { useId } from 'react';
import {
Button,
Heading,
Sheet,
SheetContent,
SheetHandle,
SheetTrigger,
Text,
} from '@vega-ui/react';
const names = ['Blue Harbor Cafe', 'Little Fern', 'Kiln & Co', 'Northside Roasters', 'The Daily Grind', 'Saltwater Bakery'];Layout/Overlay: sheet over a sheet
The place sheet opens a second sheet for choosing a list. Each list is a submit button in a <form method="dialog">, so a choice closes only the inner sheet and the place shows where it was saved.
import { useId, useState, type CSSProperties } from 'react';
import {
Button,
Heading,
Icon,
IconButton,
Sheet,
SheetClose,
SheetContent,
SheetHandle,
SheetTrigger,
Text,Error
Error: invalid promo code
The code is checked on submit, so the form prevents the default and the sheet is controlled: an unknown code keeps the sheet open with an error linked through aria-describedby and aria-invalid, a known one (WELCOME10, FRIDAY20) applies the discount and closes it.
import { useId, useState, type CSSProperties, type FormEvent } from 'react';
import {
Button,
Heading,
HelperText,
Icon,
IconButton,
Label,
Sheet,
SheetClose,
SheetContent,
SheetHandle,Disabled
Disabled: trigger disabled while the cart is empty
The Review order trigger is disabled until the cart has an item, and the hint below it is linked with aria-describedby while it is disabled. Add a flat white to enable it.
import { useId, useState } from 'react';
import {
Button,
Heading,
HelperText,
Icon,
IconButton,
Sheet,
SheetContent,
SheetHandle,
SheetTrigger,
Text,Edge
Edge: stepped snap points
With steppedSnapPoints a drag or flick moves the sheet one snap point at a time, however fast it is, through quarter steps of the screen. The line under the heading follows onChangeActiveSnapPoint.
import { useId, useState } from 'react';
import {
Button,
Heading,
Sheet,
SheetContent,
SheetHandle,
SheetTrigger,
Text,
} from '@vega-ui/react';
const snapPoints: Array<`${number}px` | number> = [0.25, 0.5, 0.75, 1];Edge: step that must be answered
With closedBy='none' a swipe, a backdrop click or Escape does not close the sheet, and there is no close button; only the two answers in the <form method="dialog"> do. The page reads the chosen returnValue in onClose.
import { useId, useState } from 'react';
import {
Button,
Heading,
Sheet,
SheetContent,
SheetTrigger,
Text,
} from '@vega-ui/react';
export default function BlockingStepSheet() {
const titleId = useId();Anatomy
Overview
Sheet owns open state, dismissal behavior, and panel coordination across its exported parts. It is a compound component because the root and child parts share one interaction model.
Required Parts
Sheet
Required. Owns open state and interaction lifecycle.
SheetTrigger
Required for trigger-driven flows. Opens the sheet from a button or other action surface.
SheetContent
Required. Renders the visible panel surface.
- Owns: the native modal
<dialog>, its::backdrop, drag gestures, and open and close transitions - Renders into
document.body, so no portal part is needed
Optional Parts
SheetHandle
Optional drag affordance, especially useful for mobile-first sheets. With several snap points it is a button that moves the sheet to the next snap point; otherwise it is hidden from assistive technology.
SheetClose
Closes the sheet 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 sheet open - Give an icon-only close button an
aria-label
Your Own Markup
The library does not ship header or body parts. They carry no behavior that plain markup cannot express, so you compose them yourself. SheetContent provides the horizontal and bottom padding.
Heading
Names the sheet. A name is required: reference a heading's id with aria-labelledby on SheetContent, or set aria-label on SheetContent.
Scrollable region
An element with overflow: auto. As a flex item of SheetContent it shrinks to the available height on its own.
Action buttons
Buttons that answer the sheet go in a <form method="dialog">: submitting it closes the sheet through onChangeOpen(false), and the submit button's value becomes the dialog's returnValue. Use SheetClose for a standalone close button.
Composition Order
SheetSheetTriggerSheetContentSheetHandle- Your heading, an optional
SheetClose, and body
Valid Composition Patterns
import { useId, type CSSProperties } from 'react';
import {
Button,
Heading,
Icon,
IconButton,
Sheet,
SheetClose,
SheetContent,
SheetHandle,
SheetTrigger,
Text,Invalid Composition Patterns
SheetContent outside Sheet
This breaks state sharing and dismissal behavior.
Content without a name
SheetContent does not name itself. Without aria-labelledby or aria-label, screen readers announce an unnamed dialog.
Complex desktop modal flow in a cramped bottom sheet
That usually means Dialog or Drawer is the better pattern.
Snap-point layout without testing real content height
Intermediate states can become unusable if form controls or actions are clipped.