Mobile-first overlay rising from the bottom edge.
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | false | Controlled visibility |
| onClose | (reason) => void | — | Escape, scrim, close button, or drag-down |
| size | compact · default | default | Max-height 46% / 86% of the viewport |
| handle | boolean | true | Shows the grabber and enables drag-to-dismiss |
| title | string | — | Labels the sheet for assistive tech |
| dismissable | boolean | true | When false, only an action closes it |
| Trigger | Result |
|---|---|
| Drag down | Past a threshold, the sheet dismisses; short drags spring back |
| Scrim tap | Closes; taps inside the sheet do not |
| Escape | Closes (desktop / keyboard) |
| On open / close | Body scroll locks; focus moves in, then back to the trigger |
A bottom sheet is the touch answer to a menu or dialog: it arrives where the thumb already is. On a pointer device the same intent is usually a menu, popover, or modal — adapt by breakpoint rather than shipping a sheet to the desktop.
Use a sheet on touch for actions and short detail — it lands where the thumb already rests.
Don't ship a bottom sheet to the desktop. There, the same intent is a menu or popover.
Keep the handle. It signals the sheet is draggable and gives a clear, forgiving way to dismiss.
Don't cram a long flow into a sheet. Past a screen or two of content, use a full page.
The long-press or ⋯ menu on mobile: full-width rows, a danger row in red.
Read a record or preview a file without leaving the list underneath.
A compact confirm rides at the bottom, its actions within a thumb's arc.
| Token | Value | Role |
|---|---|---|
| --sheet-max-h | 86% · 46% | Default and compact heights |
| --sheet-bg | #101114 | Sheet surface |
| --sheet-border | #23242A | Top hairline |
| --sheet-radius | 18px 18px 0 0 | Rounded top corners only |
| --sheet-handle | #33343A · 36×4 | Grabber pill |
| --sheet-shadow | 0 -18px 50px rgba(0,0,0,.5) | Cast upward onto the page |
| --sheet-safe | env(safe-area-inset-bottom) | Footer padding above the home bar |
| --sheet-ease | cubic-bezier(.22,1,.36,1) | Rise, 0.32s |
<div class="o-sheet-scrim is-open">
<div class="o-sheet" role="dialog" aria-modal="true">
<div class="o-sheet__handle"></div>
<div class="o-sheet__head">
<div>
<h2 class="o-sheet__title">Component details</h2>
<p class="o-sheet__sub">Button · Forms · v3.2</p>
</div>
<button class="o-sheet__close" aria-label="Close">✕</button>
</div>
<div class="o-sheet__body"><!-- scrolls --></div>
<div class="o-sheet__foot">
<button class="o-btn o-btn--ghost">Cancel</button>
<button class="o-btn o-btn--primary">Add to page</button>
</div>
</div>
</div>