Bottom Sheet

Mobile-first overlay rising from the bottom edge.

v1.0.0·Updated Jul 8, 2026 · 10:45
Live Demo — Fully Interactive
The mobile action menu, from the thumb's edge
Configuration
Height
Content
Handle
Title
Component details
Subtitle
Content rises from the bottom, within thumb reach.
CancelConfirm
<BottomSheet title="Component details" />
Behavior — The Engineering Contract
Props
PropTypeDefaultDescription
openbooleanfalseControlled visibility
onClose(reason) => voidEscape, scrim, close button, or drag-down
sizecompact · defaultdefaultMax-height 46% / 86% of the viewport
handlebooleantrueShows the grabber and enables drag-to-dismiss
titlestringLabels the sheet for assistive tech
dismissablebooleantrueWhen false, only an action closes it
Events & keyboard
TriggerResult
Drag downPast a threshold, the sheet dismisses; short drags spring back
Scrim tapCloses; taps inside the sheet do not
EscapeCloses (desktop / keyboard)
On open / closeBody 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.

Anatomy
Details
Scrolling content.
Confirm
124563
1ScrimDims the screen and catches the dismiss tap.
2SheetRises from the bottom edge, rounded at the top, capped below the status area.
3HandleThe 36×4 grabber — a signal you can drag it down to dismiss.
4HeaderOptional title, subtitle and close, for detail sheets.
5BodyAction rows or content, scrolling on its own.
6FooterOptional sticky actions, kept clear of the home indicator.
Do's and Don'ts
✓ Do
Touch · action menu from the thumb

Use a sheet on touch for actions and short detail — it lands where the thumb already rests.

✕ Don't
Full sheet on a 1440px desktop

Don't ship a bottom sheet to the desktop. There, the same intent is a menu or popover.

✓ Do

Keep the handle. It signals the sheet is draggable and gives a clear, forgiving way to dismiss.

✕ Don't
A 6-step wizard in one sheet

Don't cram a long flow into a sheet. Past a screen or two of content, use a full page.

In context: Examples
A · Action menu
ShareDuplicateDelete

The long-press or ⋯ menu on mobile: full-width rows, a danger row in red.

B · Detail sheet
Component
Preview and details rise into reach.

Read a record or preview a file without leaving the list underneath.

C · Confirm
Delete this draft?
KeepDelete

A compact confirm rides at the bottom, its actions within a thumb's arc.

Measurements & Tokens
TokenValueRole
--sheet-max-h86% · 46%Default and compact heights
--sheet-bg#101114Sheet surface
--sheet-border#23242ATop hairline
--sheet-radius18px 18px 0 0Rounded top corners only
--sheet-handle#33343A · 36×4Grabber pill
--sheet-shadow0 -18px 50px rgba(0,0,0,.5)Cast upward onto the page
--sheet-safeenv(safe-area-inset-bottom)Footer padding above the home bar
--sheet-easecubic-bezier(.22,1,.36,1)Rise, 0.32s
Code
<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>