Sheet
A sheet is a dialog. It slides up from the bottom as a tearsheet, sits in the middle as a modal, or slides in from the left as a drawer. The insides are the same in all three.
- CSS
css/components/sheet.css- JS
js/components/sheet.js
01 — Variants
Tearsheet, modal and drawers
Each button opens a real sheet. The left drawer here is the site’s menu; the right one shows the details of one record.
Details drawer
.sheet--end slides in from the right, for the details of one thing: a pod, an order, a file. Its title is a heading, with a small label above it (.sheet__eyebrow) for the kind of thing or where it lives. A short form title such as “New post” can stay a label.
Pod · default
api-7f9c4d-x2k8q
<dialog class="sheet sheet--end" id="pod-sheet" data-component="sheet" aria-labelledby="pod-title">
<div class="sheet__panel panel panel--content">
<header class="sheet__head">
<div class="sheet__titles">
<p class="sheet__eyebrow label">Pod · default</p>
<h2 class="sheet__title" id="pod-title">api-7f9c4d-x2k8q</h2>
</div>
<button class="btn btn--ghost btn--icon" type="button" data-sheet-close aria-label="Close">…</button>
</header>
<div class="sheet__body">…</div>
</div>
</dialog>
Markup
Swap .sheet--bottom for .sheet--center, .sheet--start or .sheet--end. Any element with data-sheet-open="id" opens it; data-sheet-close closes it.
02 — Behavior
Script
| Name | Detail |
|---|---|
| Focus | Moves into the sheet, stays there, and returns to the button that opened it. |
| Closing | Esc, the close button, or a click on the dimmed page. A form with unsaved changes asks first. |
| Phones | The tearsheet goes full height and shrinks above the on-screen keyboard. The page can’t scroll behind it. |
jewel:sheetopen, jewel:sheetclose | Fire on the dialog. |
dialog.jewelSheet | .open(opener), .close({ force }), .requestClose(). |
03 — Usage
Use it for
- Tearsheet: a task people finish and leave, such as a post form. Best on phones.
- Modal: a short decision or a small form.
- Drawer: navigation on phones.
Avoid
- Content people need to read alongside the page.
- A sheet that opens another sheet.
- Warnings that block for no reason. Use a notice in the page.
04 — Accessibility
Notes
- A native
<dialog>, labeled by its title witharia-labelledby. - The close button is an icon button with
aria-label="Close".
05 — Without Jewel’s JS
What the script does
The script opens and closes the <dialog>, asks before discarding a changed form, stops the page scrolling and returns focus. The CSS reads only the markup below, so a framework that owns open and closed can drive it and leave out sheet.js and data-component.
| State | Set this | Notes |
|---|---|---|
| Open | dialog.showModal() | Not the open attribute alone: the backdrop, top layer and inert page come from the modal. In React, call it and close() from an effect when your state changes. |
| Closed | dialog.close() | CSS slides it out once [open] goes. |
| Page scroll | has-open-sheet on <html> while it’s open | Stops the page scrolling behind it. |
| Height on phones | --sheet-vh on the dialog, in px | Optional. The script sets it to visualViewport.height so the sheet shrinks above the on-screen keyboard. Without it, 100dvh. |
| Discard bar | <div class="sheet__confirm" role="alertdialog"> after .sheet__head | Optional. The script adds one with Keep editing and Discard buttons, hidden until a changed form is closed. |
| Openers | Your own click handlers | data-sheet-open and data-sheet-close are only read by the script. |
Events and keyboard
- Esc fires the dialog’s
cancelevent. CallpreventDefault()if you want to ask first; either way, update your state on the dialog’scloseevent. - A click on the backdrop lands on the dialog itself (
event.target === dialog). The script also checks that the press started there, so a text selection dragged outside doesn’t close it. - On open, the script focuses
[autofocus]or the first field in.sheet__body, except on touch screens. On close, return focus to the button that opened it. - While it’s open, keep the focused field in view once the on-screen keyboard is up.
jewel:sheetopenandjewel:sheetclosedon’t fire without the script.