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

Sheet

Sheet is a compound component for panel-style overlays, usually bottom-mounted or mobile-first, with trigger, content, and handle parts.

Labeling

SheetContent does not name itself. Naming the sheet is your job, and it is required: a sheet without a name is inaccessible, because screen readers announce it only as "dialog". Reference a visible heading with aria-labelledby on SheetContent, or set aria-label when there is no visible heading. Non-closable sheets should explain why the user is in the flow and how to complete it.

sheet-with-heading-and-close-button.tsx · 71 lines
import { useId, useState, type CSSProperties } from 'react';
import {
  Button,
  Heading,
  Icon,
  IconButton,
  Sheet,
  SheetClose,
  SheetContent,
  SheetHandle,
  SheetTrigger,
  Text,

Keyboard Behavior

AnnouncementFocus the demo and press a key from the table.
KeyAction
EnterSpaceOn the trigger: opens the sheet. The sheet itself takes the focus, or the child with autoFocus.
TabShift + TabMoves the focus to the next or previous control inside the sheet. The page behind is inert, so the focus does not reach it. A SheetHandle without several snap points is disabled and skipped.
EnterSpaceOn SheetHandle with several snap points: moves the sheet to the next higher snap point, and from the highest one back to the lowest.
EscapeCloses the sheet and returns the focus to the element that opened it. With closedBy='none' it does nothing.
Every row is checked in a real browser: Playwright presses the key in Chromium and checks where the focus and the active item land.

SheetHandle is always a <button> and has no name of its own: give it one with aria-label, for example Resize sheet. Without several snap points it is disabled and aria-hidden, so it is not announced, and the sheet can still be dragged by it.

Focus Behavior

  • Focus should move into the sheet on open.
  • Focus should restore sensibly on close.

Focus handling comes from the native <dialog>: the rest of the page is inert while the sheet is open, Tab stays inside the sheet (it can still reach the browser UI), and closing restores focus to the element that was focused before opening.

On open the sheet itself takes focus, so nothing inside is highlighted and screen readers announce its title; Tab moves to the first control. Put autoFocus on a child to focus it instead, for example the first field of a form.

Screen Reader Semantics

  • The sheet is exposed as role=dialog with aria-modal by the browser and is named by your aria-labelledby or aria-label.
  • The trigger exposes aria-haspopup='dialog' and aria-expanded.
  • SheetClose has no default label, so an icon-only button inside it needs an aria-label; the library ships no built-in strings.
  • The sheet should read as one coherent overlay surface.
  • Non-closable sheets need especially clear purpose.

Form Semantics

  • If the sheet contains a form, required fields and error messaging should remain visible at every snap point.
  • Gesture-friendly layout should not hide the real submit and dismissal semantics.
  • Buttons in a <form method="dialog"> close the sheet through onChangeOpen(false), and the submit button's value becomes the dialog's returnValue. This also works with closedBy='none', so a non-closable sheet still has a completion path.

Accessibility Risks

  • A sheet without aria-labelledby or aria-label.
  • Relying only on gesture understanding.
  • Snap points that create unusable partially visible content.
  • Non-closable flows without clear completion path.