Sheet
<button class="btn btn-outline" data-pk-dialog-open="demo-sheet">Open sheet</button>
<dialog class="sheet" id="demo-sheet"> <div class="sheet-header"> <h3 class="sheet-title">Edit profile</h3> <p class="sheet-description"> Make changes to your profile here. Click save when you are done. </p> </div>
<div class="sheet-body"> <div class="field-group"> <div class="field"> <label class="label" for="sheet-name">Name</label> <input class="input" id="sheet-name" value="Ada Lovelace" /> </div> <div class="field"> <label class="label" for="sheet-username">Username</label> <input class="input" id="sheet-username" value="@ada" /> </div> </div> </div>
<div class="sheet-footer"> <button class="btn" data-pk-dialog-close="save">Save changes</button> <button class="btn btn-outline" data-pk-dialog-close>Close</button> </div>
<button class="btn btn-ghost btn-icon-sm sheet-close" data-pk-dialog-close> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M18 6 6 18M6 6l12 12"></path> </svg> <span class="sr-only">Close</span> </button></dialog><button class="btn btn-outline" data-pk-dialog-open="filters">Open sheet</button>
<dialog class="sheet" id="filters" data-side="right"> <div class="sheet-header"> <h3 class="sheet-title">Edit profile</h3> <p class="sheet-description">Make changes to your profile here.</p> </div> <div class="sheet-body">…</div> <div class="sheet-footer">…</div></dialog>A sheet is a <dialog> pinned to an edge, so it is modal for free: the page behind it is inert,
Escape closes it, and focus is trapped and restored by the browser.
| Class | Part |
|---|---|
.sheet | The panel |
.sheet-header | Title and description |
.sheet-title | Names the panel, and labels it for screen readers |
.sheet-description | Supporting line |
.sheet-body | The scrolling middle |
.sheet-footer | Actions, pushed to the bottom |
.sheet-close | The close button in the corner |
| Attribute | Meaning |
|---|---|
data-side="right" | Default. Also left, top, bottom |
data-side="inline-start" / "inline-end" | Sides that follow the reading direction |
data-pk-dialog-open | On the trigger: the id of the sheet |
data-pk-dialog-close | On a control inside: closes, with an optional return value |
data-pk-static | Clicking the backdrop does not close it |
<button class="btn btn-outline" data-pk-dialog-open="sheet-top">Top</button><button class="btn btn-outline" data-pk-dialog-open="sheet-right">Right</button><button class="btn btn-outline" data-pk-dialog-open="sheet-bottom">Bottom</button><button class="btn btn-outline" data-pk-dialog-open="sheet-left">Left</button>
<dialog class="sheet" id="sheet-top" data-side="top"> <div class="sheet-header"> <h3 class="sheet-title">Top</h3> <p class="sheet-description">Press Escape to close.</p> </div></dialog>
<dialog class="sheet" id="sheet-right" data-side="right"> <div class="sheet-header"> <h3 class="sheet-title">Right</h3> <p class="sheet-description">Press Escape to close.</p> </div></dialog>
<dialog class="sheet" id="sheet-bottom" data-side="bottom"> <div class="sheet-header"> <h3 class="sheet-title">Bottom</h3> <p class="sheet-description">Press Escape to close.</p> </div></dialog>
<dialog class="sheet" id="sheet-left" data-side="left"> <div class="sheet-header"> <h3 class="sheet-title">Left</h3> <p class="sheet-description">Press Escape to close.</p> </div></dialog>left and right are literal: a right sheet stays on the right in Arabic. Use inline-start and
inline-end when the panel should follow the reading direction instead.
Width is not baked in — a side sheet is three quarters of the screen up to sm, and anything
beyond that is yours to set.
<button class="btn btn-outline" data-pk-dialog-open="sheet-wide">Open wide sheet</button>
<dialog class="sheet" id="sheet-wide" data-side="right" style="--sheet-width: 32rem; width: var(--sheet-width); max-width: 100%"> <div class="sheet-header"> <h3 class="sheet-title">Filters</h3> <p class="sheet-description">A sheet is as wide as you make it.</p> </div>
<div class="sheet-body"> <div class="field-group"> <label class="label"> <input class="checkbox" type="checkbox" checked /> In stock </label> <label class="label"> <input class="checkbox" type="checkbox" /> On sale </label> </div> </div>
<div class="sheet-footer"> <button class="btn" data-pk-dialog-close="apply">Apply</button> </div></dialog>Set dir="rtl" on the page or on any subtree. Named sides stay physical; the inline-* sides
swap. See the button page for a live example.
Accessibility
Section titled “Accessibility”.sheet-titlelabels the panel througharia-labelledby, so it is announced by name. Without a title, give the sheet anaria-label.- The browser makes it modal.
showModal()marks the rest of the page inert, traps Tab, and returns focus to whatever opened it — none of which needs a focus-trap library. - A close button needs text. The × is an icon; keep the
.sr-onlylabel beside it. - Do not put the only way out in the corner. Escape and the backdrop already close it; a visible Cancel is still worth having when there is something to lose.
- The body scrolls, not the panel. Keeping the header and footer fixed means the actions stay reachable in a long form.