Sheets
A surface docked to an edge of the screen. It holds content secondary to what is behind it. Dock it to the bottom for a bottom sheet, or to a side for a side sheet.
Usage
A sheet is a <dialog>. A modal sheet takes focus, keeps the page behind it
unreachable while it is open, and gives focus back when it closes. Clicking the scrim or pressing Escape closes it.
Give the sheet an id and point a trigger at it with command="show-modal" and commandfor. command="close" closes it.
This needs no script or state, and it works before the page has hydrated.
<script lang="ts">
import { Button, IconButton, Item, Sheet } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<Button command="show-modal" commandfor="bottom-sheet">Open bottom sheet</Button>
<Sheet id="bottom-sheet" headline="Bottom sheet">
{#snippet action()}
<IconButton title="Close" command="close" commandfor="bottom-sheet">
<Icon>close</Icon>
</IconButton>
{/snippet}
<Item type="button" command="close" commandfor="bottom-sheet">Share</Item>
<Item type="button" command="close" commandfor="bottom-sheet">Add to favourites</Item>
<Item type="button" command="close" commandfor="bottom-sheet">Delete</Item>
</Sheet>
Placement
placement picks the edge. bottom and top span the width. start and end run the full height and make a side sheet. The drag handle is
only drawn on a bottom sheet.
<script lang="ts">
import { Button, IconButton, Sheet } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<Button command="show-modal" commandfor="side-sheet">Open side sheet</Button>
<Sheet id="side-sheet" placement="end" headline="Side sheet">
{#snippet action()}
<IconButton title="Close" command="close" commandfor="side-sheet">
<Icon>close</Icon>
</IconButton>
{/snippet}
<p>Filters, details, or anything secondary to the page behind it.</p>
</Sheet>
Heights
A bottom sheet opens at most half the window high. Selecting the drag handle (a click, Space or Enter) raises a taller sheet to its full height and lowers it
again. A sheet that already shows everything closes instead. You can also drag the handle: up to
raise the sheet, down to lower or close it. The sheet settles on the nearest height. Raised, it
keeps 72dp free at the top. In a window wider than 640dp it keeps 56dp at the top and 56dp at the
sides. bind:expanded reads and sets the height.
<script lang="ts">
import { Button, Item, Sheet } from 'noph-ui'
const places = Array.from({ length: 24 }, (_, index) => `Place ${index + 1}`)
</script>
<Button command="show-modal" commandfor="heights-sheet">Open places</Button>
<Sheet id="heights-sheet" headline="Places nearby">
{#each places as place (place)}
<Item type="button" command="close" commandfor="heights-sheet">{place}</Item>
{/each}
</Sheet>
Side sheet
A side sheet can start its header with a back button through leading and show actions
along the bottom through actions. detached holds it 16dp from the window's
edges with every corner rounded.
<script lang="ts">
import { Button, IconButton, Sheet, TextField } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<Button command="show-modal" commandfor="filter-sheet">Open filters</Button>
<Sheet id="filter-sheet" placement="end" detached headline="Filters">
{#snippet leading()}
<IconButton title="Back" command="close" commandfor="filter-sheet">
<Icon>arrow_back</Icon>
</IconButton>
{/snippet}
{#snippet action()}
<IconButton title="Close" command="close" commandfor="filter-sheet">
<Icon>close</Icon>
</IconButton>
{/snippet}
<TextField label="Keyword" />
{#snippet actions()}
<Button command="close" commandfor="filter-sheet">Apply</Button>
<Button variant="outlined" command="close" commandfor="filter-sheet">Cancel</Button>
{/snippet}
</Sheet>
Standard
modal=false makes a standard sheet. The rest of the page stays usable, with no scrim
and no focus trap. Clicking outside does not close it, so close it yourself. A standard side sheet is
part of the layout: place it in a row beside the content, which shrinks to make room as the sheet opens.
A trigger cannot open a standard sheet, because invoker commands only open modal dialogs. Bind open, or call show() and close() on the component. command="close" still closes it.
<script lang="ts">
import { Button, IconButton, Sheet } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
let open = $state(false)
</script>
<div style="display:flex;width:100%;min-height:16rem;overflow:hidden">
<div style="flex:1;min-width:0;padding:1rem">
<Button onclick={() => (open = !open)}>{open ? 'Close' : 'Open'} standard sheet</Button>
<p>The content makes room for the sheet and stays interactive.</p>
</div>
<Sheet bind:open modal={false} placement="end" headline="Standard">
{#snippet action()}
<IconButton title="Close" onclick={() => (open = false)}>
<Icon>close</Icon>
</IconButton>
{/snippet}
<p>A standard side sheet sits beside the content.</p>
</Sheet>
</div>
Theming
| Custom property | Description |
|---|---|
--np-sheet-container-color | Background. |
--np-sheet-shape | Corner radius on the exposed edges. |
--np-sheet-max-width | Widest a bottom sheet gets before it centers. Defaults to 40rem. |
--np-sheet-size | Height of a bottom or top sheet, width of a side sheet. |
--np-sheet-handle-color | The drag handle. |
--np-sheet-elevation | Shadow. |
Accessibility
The sheet is a native <dialog>. A modal sheet opens with showModal, so the browser keeps focus inside it, marks the rest of the page inert and closes it on Escape or a click on the scrim. A standard sheet stays part of the
page, and focus moves in and out of it as usual.
The headline names the sheet through aria-labelledby and renders as a
level two heading. Change the level with headlineLevel to fit your page outline. A
sheet without a headline needs an aria-label. The drag handle is a button named by handleLabel ("Drag handle" by default). Its aria-expanded says whether the
sheet is raised. Only the handle responds to a pointer drag, so the content scrolls as usual.
API
Renders a <dialog> and takes its attributes. bind:element gives
you that element. show() and close() open and close it from outside.
| Attribute | Type | Default | Description |
|---|---|---|---|
open | boolean | false | Bindable. Whether the sheet is showing. |
modal | boolean | true | When true, blocks the page behind it and adds a scrim. When false, sits beside the page. |
placement | 'bottom' | 'top' | 'start' | 'end' | 'bottom' | Which edge it docks to. |
handle | boolean | true | The drag handle. Drawn on a bottom sheet only. |
handleLabel | string | 'Drag handle' | Names the drag handle. |
expanded | boolean | false | Whether a bottom sheet is raised to its full height. Bindable. |
detached | boolean | false | Holds a side sheet 16dp from the window's edges, with every corner rounded. |
leading | Snippet | undefined | undefined | Content before the headline, such as a back button. |
headline | string | undefined | undefined | Names the sheet. Assistive technology announces it. |
headlineLevel | 1 | 2 | 3 | 4 | 5 | 6 | 2 | Heading level the headline renders as. Set it to fit your page outline. |
action | Snippet | undefined | undefined | Trailing action in the header, usually a close button. |
actions | Snippet | undefined | undefined | Actions along the bottom of the sheet. |