Sheet

Opens the details of one thing at the side.

Side

  • enddefault

  • start

  • bottom

Modal

    • job_4812
    • job_4811
    • job_4810

    false

States

  • open

  • pending

  • failed

Usage

When to use it
Use
The details of one row in a list: a job, a partition, a worker, read beside or over the list.
Use
A long form that needs the height of the screen: a pipeline's schedule and retries.
Use
On a phone, the bottom sheet for a short choice that a dialog would crowd.
Not for
One question or a short form that blocks the page. Use Dialog.
Not for
A few lines next to a control. Use Popover.
Not for
Panels of one view the person switches between. Use Tabs.

Rules

7 rules
Must
End the footer with SheetClose and at most one SheetAction. Because the footer is one group of actions, and the slab marks the one thing to act on.
Should
Dock the sheet (modal={false}) when the person moves through rows and reads each one's details; use the modal sheet for one look. Because a docked sheet keeps the list usable, so the next row is one press away without closing anything.
Must
Leave side at "end" for details, or use "start" and "end", never "left" and "right", unless the panel belongs to a screen edge. Because end flips to the left in right-to-left text, where the reading order puts details.
Must
Set an ID, a path or a number people compare with SheetField code; leave sentences in the sans. Because values compared character by character read in mono as written, while a sentence in mono reads slowly.
Must
Return the request's promise from SheetAction's onAction; never close the sheet in onClick. Because onAction holds the sheet pending and shows a failure above the footer, where onClick closes over a failed request.
Must
Name the sheet in SheetHeader in mono as written, put SheetDescription first in SheetBody, and leave the insets to the parts. Because the header is the 38px region header every frame opens with, and header, body and footer share the 12px inset that the list beside the sheet starts on too.
Never
Box the sections of a sheet in frames or panels; let SheetSection rule them. Because the sheet is the region's one edge, and a box inside it is a box in a box.

State order

3 states, highest first
1
pending
2
failed
3
open

Accessibility

4 checks
A modal sheet is role="dialog", named by SheetTitle; the page behind is inert and focus returns to the trigger.
A docked sheet is an aside named by its title; Escape inside it closes it and returns focus to its trigger.
The docked resize rule is a focusable separator with its width as aria-valuenow; arrows step it by 32px, Shift by 8.
Field labels are text-2 at 7.8:1 and 10.2:1; values text-1.

Keyboard

4 keys
Escape
Closes the sheet and returns focus to the trigger; nothing while an action runs.
Tab
Moves through the sheet's controls; a modal sheet wraps from last to first.
Left, Right
On the docked resize rule: narrows or widens it by 32px, 8 with Shift.
Home, End, Enter
On the docked resize rule: narrowest, widest, back to the starting width.

Edge cases

6 edge cases
Phone, 390 wide
A side sheet leaves 40px of page beside it; a docked sheet takes the full width.
Swipe
On touch, a swipe toward the sheet's edge dismisses it, and the scrim fades with the finger.
Right to left
end is the left edge; the edge line and the resize rule follow.
Long values
A code value breaks anywhere; a sentence wraps; the label column truncates.
Dark theme
The panel sits a step above the near-black page with its rule edge on the inner side.
Reduced motion
It fades without the 16px slide.

API

11 props
Sheet modal
boolean default true
Sheet open, defaultOpen, onOpenChange
Base UI default false
SheetContent side
'end' | 'start' | 'top' | 'bottom' | 'left' | 'right' default 'end'
SheetContent showCloseButton
boolean default not on bottom
SheetHeader meta
ReactNode
SheetContent defaultWidth, minWidth, maxWidth
number default 360, 280, 640
SheetContent onWidthChange
(width) => void
SheetFooter defaultAction, meta
as Dialog default auto
SheetAction onAction
() => void | Promise
SheetSection title
ReactNode
SheetField label, code
ReactNode, boolean

Tokens

30 tokens
--panel
#fafaf7
--page
#ffffff
--line-control
#8a8478
--line
#e2dfd5
--line-faint
#efede6
--scrim
rgb(22 21 18 / 0.32)
--overlay
480px
--inset
12px
--head
38px
--space-4
16px
--space-10
40px
--row
40px
--control-sm
26px
--type-body-sm
400 14px/20px var(--font-sans)
--type-label-sm
400 12px/16px var(--font-mono)
--type-micro
400 11px/14px var(--font-mono)
--type-code
400 13px/20px var(--font-mono)
--type-code-sm
400 12px/16px var(--font-mono)
--text-1
#161512
--text-2
#55524a
--text-3
#6b675e
--focus
#7a5d00
--fail
#b3261e
--fail-tint
#fdecea
--fail-text
#b3261e
--z-overlay
50
--dur-overlay
160ms
--dur-state
120ms
--ease-out
cubic-bezier(0.2, 0.8, 0.2, 1)
--ease-in
cubic-bezier(0.4, 0, 1, 1)

Install

For your coding agent

GitHub

Please star me pookie

198

A new design system every week

Leave your email before you copy the prompt.