Sheet
StableA panel that slides over the page from the bottom, right or left edge, with a drag handle on bottom sheets.
Also known as drawer, bottom sheet, side sheet, off canvas
Install with npm install @tattva/ui (setup), or copy the code from the kit in Downloads.
Bottom sheet with a case summary
function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="secondary" onClick={() => setOpen(true)}>Open case summary</Button>
<Sheet open={open} onClose={() => setOpen(false)} title="Case summary"
footer={<Button onClick={() => setOpen(false)}>Done</Button>}>
<div className="flex flex-col gap-3">
<Countdown deadline="2026-10-27" today="2026-10-07" start="2026-09-27" label="They must reply by" />
<p className="text-fg-muted">Flipkart, <Money value={2340} calculated /> refund requested.</p>
</div>
</Sheet>
</>
);
}Opens from the bottom edge. Drag the handle down, press Escape, tap the scrim or use the Close handle to dismiss it.
When to use it
Shows a short task or a set of details on top of the page without leaving it. It feels close to the page behind it, so it suits quick looks and short decisions.
Made of: Scrim · Drag handle (bottom sheet) · Title · Close button (side sheet) · Body · Footer
Use it for
- A case summary or detail view that opens from a list on a phone.
- A side panel for filters, settings or order details.
- A step the person must finish, with dismissing turned off and buttons in the footer.
Not for
- A short question that needs a yes or no: use Dialog.
- A panel that stays beside the page while people keep working: use CanvasPanel.
- A list of actions on one item: use Menu.
Do and avoid
Do
- Keep the content short enough to scan in one sitting.
- Put the main action last in the footer.
- Use a bottom sheet on phones and a side sheet for details beside a list.
- Give the sheet a title that says what it is, such as Case summary.
Avoid
- Do not stack one sheet on another.
- Do not use it for a one-line confirmation. Use a Dialog.
- Do not turn off dismissing without a visible button that closes it.
- Do not put long forms in a bottom sheet. Use a page.
On a phone
- A bottom sheet rises from the screen edge, is capped at 40, 65 or 90 percent of the screen height by size, and scrolls inside its body.
- Drag the handle down more than 80px, or flick it, to close. The handle is 44px tall on a touch screen, and a tap on it also closes.
- The footer buttons stack in a column on a phone and sit in a row from 640px wide, and the footer keeps clear of the home bar with the bottom safe area.
- Side sheets have no drag, leave a 40px strip of the page showing, and close with a 44px button or a tap on that strip.
Properties
| Prop | Type | Default | Description |
|---|---|---|---|
| openrequired | boolean | none | Whether the sheet is showing. Fully controlled. |
| onCloserequired | () => void | none | Called when the sheet asks to close: Escape, a scrim tap, the Close control or a drag past the limit. Set open to false in response. |
| titlerequired | string | none | Rendered as the heading and used as the sheet's name through aria-labelledby. |
| side | "bottom" | "right" | "left" | "bottom" | Which edge the sheet slides in from. |
| children | ReactNode | none | The body. It scrolls when it is taller than the sheet. |
| footer | ReactNode | none | Usually buttons, pinned under the body. |
| dismissible | boolean | true | When false, Escape, a scrim tap and dragging do nothing and the Close control is not drawn. |
| size | "sm" | "md" | "lg" | "md" | Width of a side sheet. Height cap of a bottom sheet, as a share of the screen height (40, 65 or 90 percent). |
| className | string | none | Classes for the sheet panel. |
"Show code under the preview gives the code for the example on the stage."
States
States this part has beyond its default look. Build each one your screen can reach. States set by a property are in the Code tab.
- closed
- With open false nothing is drawn and the page behaves as normal.
- open
- The page behind is inert and cannot scroll, and focus moves into the sheet. Focus returns to the opener on close.
- dragging
- On a bottom sheet, holding the handle moves the sheet down with the pointer. Past 80px, or a fast flick, it closes. Otherwise it springs back.
- closing
- After open turns false the sheet slides back out (about a third of a second) before it is removed, and focus returns to the opener once it has gone. With reduced motion it is removed at once.
- scrolling body
- When the body is taller than the sheet, only the body scrolls and the title and footer stay in view.
- focus
- A visible focus ring shows on the handle, the Close button, footer buttons and body controls when reached by keyboard.
- hover
- The Close button on a side sheet gets a tinted background under the pointer.
Tokens
Change one of these and every part that uses it follows.
--scrim--surface-raised--border--border-strong--ease-out--dur-slow--shadow-lg--radius-overlay
Built in
- It is a native dialog opened with showModal, so the page behind is inert and Tab stays inside.
- The title is the heading and the name of the dialog.
- Focus moves to the first control in the body or footer, or to the sheet itself, and returns to the opener on close.
- Escape closes it when dismissible is true.
- On a bottom sheet, the drag handle is a button named Close, so a keyboard or screen reader user can close it without dragging.
- Page scrolling is locked while it is open.
- Sliding only happens when the person has not asked for reduced motion.
- Touch targets of the handle and the side sheet Close button grow to 44px on touch screens.
What you need to do
- Pass a title that names the sheet. It is the heading and the accessible name.
- Set open to false when onClose is called, or the sheet cannot close.
- When dismissible is false, give people a visible way out in the footer.
Built toward WCAG 2.2 AA and checked automatically; no independent audit yet. Read the accessibility guide