Sheet
Opens the details of one thing at the side.
Side
enddefault
start
bottom
top
Mode
modaldefault
- run_8f2a91
- run_8f2a90
- run_8f2a8c
docked
States
open
pending
failed
Usage
When to use it
- Use
- The details of one item from a list the person keeps in mind: a run, a key, an integration.
- Use
- The details of the selected row beside its list, while the list stays usable (docked).
- Use
- A short choice on a phone, from the bottom edge.
- Not for
- A yes or no question, or a form of up to three fields. Use Dialog.
- Not for
- A few lines next to the control that opened them. Use Popover.
- Not for
- The panel is the page's main subject. Give it a route.
Rules
9 rules
- Must
- Build the inside from SheetHeader, SheetBody and SheetFooter; never plain padded divs. Because the header and body together are the paper panel, the body is the only part that scrolls, and the footer is the shell's row.
- Must
- End the footer with SheetClose (paper) and at most one SheetAction. Because the footer is one group of actions, and two ink buttons in it split the eye.
- Never
- Put a shell card inside the body; group rows with SheetSection. Because the sheet is already the card, and a second shell inside its panel is a box in a box.
- Must
- Title the sheet with the item's name or ID and put context in SheetDescription. Because the title is the sheet's accessible name, so "Details" tells a screen reader nothing about which run is open.
- Should
- Use modal={false} for the details of a selected row; a modal sheet hides the list the person is working through.
- Must
- Open a docked panel with SheetTrigger and close it with SheetClose. Because the docked panel returns focus on Escape to the element SheetTrigger registered, so a plain button leaves focus lost.
- Must
- Give actions in a side="bottom" sheet the default size: render={<Button variant="secondary"/>} on SheetClose and size="default" on SheetAction. Because a bottom sheet is the phone layout, and a 26px small button is too short a target for a thumb.
- Should
- Pass mono to SheetField for an ID, a hash or a key.
- Never
- Add a shadow or a border to SheetContent; the ring and the scrim are its lift. Because Glaze has no shadows, and a border on top of the ring doubles the edge.
State order
3 states, highest first
- 1
- pending
- 2
- failed
- 3
- open
Accessibility
5 checks
- A modal sheet is a dialog named by SheetTitle; the page behind is inert and Tab cycles inside.
- A docked sheet is an aside labelled by its title; Escape inside it closes it and returns focus to its trigger.
- The resize handle is a focusable separator with its width as the value, moved by the arrow keys.
- The body takes a tab stop only while it has more to scroll.
- A failed action shows a role="alert" message above the footer.
Keyboard
4 keys
- Escape
- Closes the sheet and returns focus to its trigger; nothing while an action runs.
- Enter
- Presses the footer's default action when focus is not on a control.
- Left and Right arrows
- On the docked handle: narrow or widen by 32px, 8px with Shift.
- Home, End, Enter
- On the docked handle: the narrowest, the widest, the starting width.
Edge cases
6 edge cases
- Sixty fields
- Only the body scrolls; a hairline shows under the header once it has.
- Phone, 390 wide
- A side sheet leaves 40px of page beside it; a docked sheet takes the full width under its list.
- Right to left
- end is the left edge, the swipe to dismiss runs left and the resize handle moves to the panel's right.
- Swipe on touch
- The panel follows the finger toward its edge and the scrim fades with it.
- A slow action
- The arc turns; Close, the X, Escape, a swipe and the scrim wait for it.
- Reduced motion
- The panel fades without travelling.
API
10 props
Sheet modalbooleandefaulttrueSheet open, defaultOpen, onOpenChangeboolean, (open) => voiddefaultfalseSheet containedbooleandefaultfalseSheetContent side'start' | 'end' | 'top' | 'right' | 'bottom' | 'left'default'end'SheetContent showCloseButtonbooleandefaultnot on bottomSheetContent defaultWidth, minWidth, maxWidthnumberdefault360, 280, 640SheetContent onWidthChange(width) => voidSheetFooter defaultAction, meta'primary' | 'dismiss' | 'none', ReactNodedefaultautoSheetAction onAction, close() => void | Promise, booleanSheetField monobooleandefaultfalse
Tokens
25 tokens
--shell- #f1f1f0
--paper- #ffffff
--line- rgb(5 5 5 / 0.10)
--line-faint- rgb(5 5 5 / 0.06)
--line-control- rgb(5 5 5 / 0.15)
--scrim- rgb(5 5 5 / 0.24)
--radius-shell- 14px
--radius-panel- 11px
--shell-inset- 3px
--overlay- 480px
--overlay-lg- 640px
--space-2- 8px
--space-4- 16px
--space-5- 20px
--space-10- 40px
--row- 36px
--type-heading-sm- 500 16px/1.35 var(--font-sans)
--type-body-sm- 400 13px/1.5 var(--font-sans)
--type-code-sm- 400 calc(13px * var(--mono-scale))/1.5 var(--font-mono)
--fail-tint- #fbe9e5
--fail-text- #b23c29
--z-overlay- 50
--dur-overlay- 200ms
--dur-state- 160ms
--ease-out- cubic-bezier(0.2, 0.8, 0.2, 1)