Use a dialog.bottom-sheet for additional or secondary
content on compact and medium windows, not for the app's main view.
The dialog is the surface. People dismiss it to get back to the page.
Tokens follow the
M3 bottom sheet spec.
The container uses surface-container-low, with 28dp top
corners and elevation 1. Max width is 640dp. From the Medium breakpoint
the sheet is inset 56dp from the sides. It never covers the top
72dp of the viewport. The drag handle is a 32×4dp bar in a 48dp
hit target.
The same sheet has two variants:
-
Modal uses
showModal(). A scrim sits behind the sheet and the page is inert. Tap the scrim, drag the handle down, or submit a<form method="dialog">to dismiss. -
Standard uses
show(). It has no scrim, and the page stays interactive. Drag the handle down or use a close action to dismiss.
<button type="button" onclick="document.getElementById('sheet').showModal()">
Show modal
</button>
<dialog id="sheet" class="bottom-sheet" aria-labelledby="open-file-title">
<h2 id="open-file-title">Open file</h2>
<div>…</div>
<form method="dialog">
<button type="submit" class="text" value="cancel">Cancel</button>
</form>
</dialog>
Pick the variant by calling showModal() or
show(). The modal variant has no extra class. A
::before pseudo-element draws the handle. If you want a
real element in that slot, put a
drag handle first.
Handle
Drag the top 48dp (or an explicit drag handle) down to dismiss the sheet. A recent flick counts even if the pointer stops moving at release. On a short drag, pausing for at least 100ms or reversing direction lets the sheet snap back. If the body overflows, the rest of the sheet scrolls.
On touch screens, give the sheet an explicit drag handle. The handle
sets touch-action: none. Without it, the browser takes a
touch drag on the built-in band as a scroll.
<dialog class="bottom-sheet" aria-labelledby="title-title">
<span class="drag-handle" aria-hidden="true"></span>
<h2 id="title-title">Title</h2>
…
</dialog>
A <span> in that slot is decoration and does
nothing on its own. Keyboard users do not need the bar. Dragging is a
pointer gesture, and Esc already dismisses the sheet from
the keyboard.
For a visible dismiss control, make the handle a
<button> and give it a name. Activating it with a
pointer or with Enter closes the sheet, so dragging is not
the only way to use it. A drag that snaps back does not also dismiss
the sheet. The component tells the click that ends a drag apart from
a tap, so the sheet stays open.
<dialog class="bottom-sheet" aria-labelledby="title-title">
<button type="button" class="drag-handle" aria-label="Dismiss"></button>
<h2 id="title-title">Title</h2>
…
</dialog>
Add expandable to give the sheet two heights. It opens partly expanded, at --md-comp-bottom-sheet-partial-height (50% of the viewport). .expanded grows it to the full height, 72dp short of the top. The height moves on the sheet's spring.
A drag up on the handle, or a flick up, expands the sheet. A drag down from expanded collapses it. A drag down from the partial height dismisses it as usual. Closing the sheet puts it back to partial.
A handle <button> switches between the heights instead of closing. The component keeps its aria-expanded current, so name the button for the height, not for dismissal. Because the handle no longer closes the sheet, give an expandable sheet an explicit close control.
<dialog class="bottom-sheet expandable" aria-labelledby="filters-title">
<button type="button" class="drag-handle" aria-label="Sheet height" aria-expanded="false"></button>
<h2 id="filters-title">Filters</h2>
<div>…</div>
<form method="dialog">
<button type="submit" class="text">Done</button>
</form>
</dialog>
Layout
On compact windows (below 600px) the sheet is full-bleed and flush to the bottom. From the Medium breakpoint the sheet is at most 640dp wide with 56dp side inset. It always stops 72dp short of the top. On large screens a side sheet or a basic dialog is usually a better fit. The bottom sheet still lays out if you open one there.
The sheet slides in, and snaps back after a short drag, on the default spatial spring. --md-comp-bottom-sheet-motion and --md-comp-bottom-sheet-motion-duration read --md-sys-motion-spring-default-spatial and its -duration token, so motion="expressive" gives it a slight bounce. The sheet extends 24px below the viewport, so the bounce never uncovers the page under it. With reduced motion requested, it appears without moving.