Sheet

A sheet slides in from the edge of the screen to display supplementary content or actions without leaving the current page.

Theme
<div class="flex justify-center">
    <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-intro" command="show-modal">
        Open
    </sa-button>
</div>
<sa-sheet id="--sheet-intro">
    <sa-sheet-header>
        <sa-sheet-title>Edit traveller</sa-sheet-title>
        <sa-sheet-description>Update the traveller's details here. Click save when you're done.
        </sa-sheet-description>
    </sa-sheet-header>
    <sa-field-group class="grid gap-6 px-4">
        <sa-field>
            <sa-label for="--sheet-intro-name">Name</sa-label>
            <sa-input id="--sheet-intro-name" name="name" value="Ibn Battuta"/>
        </sa-field>
        <sa-field>
            <sa-label for="--sheet-intro-username">Username</sa-label>
            <sa-input id="--sheet-intro-username" name="username" value="@@ibnbattuta"/>
        </sa-field>
    </sa-field-group>
    <sa-sheet-footer>
        <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-intro" command="close">
            Cancel
        </sa-button>
        <sa-button commandfor="--sheet-intro" command="close">
            Save Changes
        </sa-button>
    </sa-sheet-footer>
</sa-sheet>

Usage

<sa-sheet> renders a standard HTML dialog element styled to slide in from the edge of the screen. Use the Invoker Commands API to open and close it by adding commandfor and command attributes to a <sa-button> element.

<sa-button commandfor="--custom-sheet" command="show-modal">
    Sheet Trigger
</sa-button>
<sa-sheet id="--custom-sheet">
    <sa-sheet-header>
        <sa-sheet-title>...</sa-sheet-title>
        <sa-sheet-description>...</sa-sheet-description>
    </sa-sheet-header>
    <!-- Sheet content goes here -->
    <sa-sheet-footer>
        <sa-button variant="ButtonVariant.Outline" commandfor="--custom-sheet" command="close">
            Close
        </sa-button>
    </sa-sheet-footer>
</sa-sheet>

Because the sheet renders a native dialog element, the same JavaScript API and events documented for the Dialog component apply here too. For more information, you can review the following MDN documentation:

Examples

Sides

Use the side attribute to control which edge of the screen the sheet slides in from. It accepts SheetSide.Top, SheetSide.Right (the default), SheetSide.Bottom, and SheetSide.Left.

Theme
<div class="flex justify-center gap-2">
    <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-sides-top" command="show-modal">
        Top
    </sa-button>
    <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-sides-right" command="show-modal">
        Right
    </sa-button>
    <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-sides-bottom" command="show-modal">
        Bottom
    </sa-button>
    <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-sides-left" command="show-modal">
        Left
    </sa-button>
</div>
<sa-sheet id="--sheet-sides-top" side="SheetSide.Top" class="data-[side=top]:h-[50vh]">
    <div class="p-4">
        Open on the top
    </div>
</sa-sheet>
<sa-sheet id="--sheet-sides-right" side="SheetSide.Right">
    <div class="p-4">
        Open on the right
    </div>
</sa-sheet>
<sa-sheet id="--sheet-sides-bottom" side="SheetSide.Bottom" class="data-[side=bottom]:h-[50vh]">
    <div class="p-4">
        Open on the bottom
    </div>
</sa-sheet>
<sa-sheet id="--sheet-sides-left" side="SheetSide.Left">
    <div class="p-4">
        Open on the left
    </div>
</sa-sheet>

Dismissing the sheet

Control how the sheet can be dismissed using the closedby attribute. The default behavior allows closing via the Esc key or a close button. Set closedby="any" to also allow clicking the backdrop (light dismiss), or closedby="none" to require explicit dismissal via a close button only.

Theme
<div class="flex justify-center gap-2">
    <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-dismiss-default" command="show-modal">
        Default Dismiss
    </sa-button>
    <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-dismiss-light" command="show-modal">
        Light Dismiss
    </sa-button>
    <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-dismiss-manual" command="show-modal">
        Manual Dismiss
    </sa-button>
</div>
<sa-sheet id="--sheet-dismiss-default">
    <sa-sheet-header>
        <sa-sheet-title>Default Dismiss</sa-sheet-title>
    </sa-sheet-header>
    <div class="p-4">
        <p>This sheet can be closed by pressing the
            <sa-kbd>Esc</sa-kbd>
            key or clicking the close button below.
        </p>
    </div>
    <sa-sheet-footer>
        <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-dismiss-default" command="close">
            Close
        </sa-button>
    </sa-sheet-footer>
</sa-sheet>
<sa-sheet id="--sheet-dismiss-light" closedby="any">
    <sa-sheet-header>
        <sa-sheet-title>Light Dismiss</sa-sheet-title>
    </sa-sheet-header>
    <div class="p-4">
        <p>This sheet can be closed by pressing the
            <sa-kbd>Esc</sa-kbd>
            key, clicking on the backdrop, or clicking the close button below.
        </p>
    </div>
    <sa-sheet-footer>
        <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-dismiss-light" command="close">
            Close
        </sa-button>
    </sa-sheet-footer>
</sa-sheet>
<sa-sheet id="--sheet-dismiss-manual" closedby="none">
    <sa-sheet-header>
        <sa-sheet-title>Manual Dismiss</sa-sheet-title>
    </sa-sheet-header>
    <div class="p-4">
        <p>This sheet can only be closed by clicking the close button below.</p>
    </div>
    <sa-sheet-footer>
        <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-dismiss-manual" command="close">
            Close
        </sa-button>
    </sa-sheet-footer>
</sa-sheet>

Without a close button

By default the sheet renders a close button in the top-right corner. Set show-close-button="false" to hide it.

Theme
<div class="flex justify-center">
    <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-no-close-button" command="show-modal">
        Open
    </sa-button>
</div>
<sa-sheet id="--sheet-no-close-button" show-close-button="false">
    <sa-sheet-header>
        <sa-sheet-title>No Close Button</sa-sheet-title>
        <sa-sheet-description>This sheet doesn't have a close button in the top-right corner. Press Esc to close.</sa-sheet-description>
    </sa-sheet-header>
</sa-sheet>

Get form content depending on return value

Add submit buttons to a form with method="dialog" and set a value on each button. Check returnValue to determine which button was clicked, then use FormData in the close event handler to read the submitted values.

If you want to return a result from the sheet, we strongly recommend using the dialog() helper which allows you to use sheets in an async/await style

Theme
<sa-stack align="StackAlign.Start" class="min-w-md">
    <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-return-form-value-sheet" command="show-modal">
        Open Sheet with Form
    </sa-button>
    <label class="text-sm font-bold">Output:</label>
    <div class="font-mono w-full h-40 overflow-y-auto rounded-md border border-border bg-muted p-3 text-foreground" id="--sheet-return-form-value-output">-</div>
</sa-stack>
<sa-sheet id="--sheet-return-form-value-sheet">
    <form method="dialog"  class="flex flex-col h-full gap-4">
        <sa-sheet-header>
            <sa-sheet-title>Edit traveller</sa-sheet-title>
            <sa-sheet-description>Update the traveller's details here. Click save when you're done.
            </sa-sheet-description>
        </sa-sheet-header>
        <sa-field-group class="grid gap-6 px-4">
            <sa-field>
                <sa-label for="--sheet-return-form-value-name">Name</sa-label>
                <sa-input id="--sheet-return-form-value-name" name="name" value="Ibn Battuta" required/>
            </sa-field>
            <sa-field>
                <sa-label for="--sheet-return-form-value-username">Username</sa-label>
                <sa-input id="--sheet-return-form-value-username" name="username" value="@@ibnbattuta"
                           required/>
            </sa-field>
        </sa-field-group>
        <sa-sheet-footer>
            <sa-button variant="ButtonVariant.Outline" type="button" commandfor="--sheet-return-form-value-sheet"
                        command="close">
                Cancel
            </sa-button>
            <sa-button type="submit" value="confirm">
                Save Changes
            </sa-button>
        </sa-sheet-footer>
    </form>
</sa-sheet>
<script>
    (function() {
        const sheet = document.getElementById("--sheet-return-form-value-sheet");
        const output = document.getElementById("--sheet-return-form-value-output");

        sheet.addEventListener("close", () => {
            const cancelled = sheet.returnValue === "" || sheet.returnValue === "cancel";
            if (cancelled) {
                output.innerHTML = "Cancelled";
                return;
            }

            const form = sheet.querySelector("form");
            const data = Object.fromEntries(new FormData(form));
            output.innerHTML = JSON.stringify(data, null, 2);
        });
        sheet.addEventListener("toggle", (e) => {
            // Reset the return value every time the dialog opens to prevent a previous
            // returnValue from being returned when pressing the Esc key
            if (e.newState === "open") {
                sheet.returnValue = "";
            }
        });
    })();
</script>

JavaScript API

Since <sa-sheet> renders a native dialog element, you can control it directly via JavaScript using the showModal(), show(), and close().

Theme
<sa-group justify="GroupJustify.Center">
    <sa-button variant="ButtonVariant.Outline" id="--sheet-js-api-show-modal-button">
        Show Modal
    </sa-button>
    <sa-button variant="ButtonVariant.Outline" id="--sheet-js-api-show-button">
        Show
    </sa-button>
</sa-group>
<sa-sheet id="--sheet-js-api-sheet">
    <sa-sheet-header>
        <sa-sheet-title>JavaScript API</sa-sheet-title>
        <sa-sheet-description>You can open and close this sheet using the dialog JS API</sa-sheet-description>
    </sa-sheet-header>
    <sa-sheet-footer>
        <sa-button variant="ButtonVariant.Outline" id="--sheet-js-api-close-button">
            Close via JS
        </sa-button>
    </sa-sheet-footer>
</sa-sheet>
<script>
    (function() {
        const sheet = document.getElementById("--sheet-js-api-sheet");
        const showModalButton = document.getElementById("--sheet-js-api-show-modal-button");
        const showButton = document.getElementById("--sheet-js-api-show-button");
        const closeButton = document.getElementById("--sheet-js-api-close-button");

        showModalButton.addEventListener("click", () => {
            sheet.showModal();
        });

        showButton.addEventListener("click", () => {
            sheet.show();
        });

        closeButton.addEventListener("click", () => {
            sheet.close();
        });
    })();
</script>

JavaScript events

The sheet fires beforetoggle and toggle events as it opens and closes, a cancel event when the user attempts to close via Esc or request-close, and a close event once closed. Call preventDefault() in beforetoggle to prevent opening, or in cancel to prevent closing.

Theme
<sa-stack gap="StackGap.Small" align="StackAlign.Start" class="min-w-md">
    <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-js-events-sheet" command="show-modal">
        Open Sheet
    </sa-button>
    <sa-input type="checkbox"
               id="--sheet-js-events-prevent-toggle-checkbox"
               label="PreventDefault() in beforetoggle event"
               description="Will prevent the sheet from opening"/>
    <label class="text-sm font-bold">Events:</label>
    <div class="font-mono w-full h-40 overflow-y-auto rounded-md border border-border bg-muted p-3 text-foreground" id="--sheet-js-events-output">
    </div>
</sa-stack>
<sa-sheet id="--sheet-js-events-sheet">
    <sa-sheet-header>
        <sa-sheet-title>Events</sa-sheet-title>
        <sa-sheet-description>This demonstrates the events on the sheet - or rather, the underlying dialog
        </sa-sheet-description>
    </sa-sheet-header>
    <div class="p-4">
        <sa-input type="checkbox"
                   id="--sheet-js-events-prevent-cancel-checkbox"
                   label="PreventDefault() in cancel event"
                   description="Will prevent the Request Close button from closing the sheet"/>
    </div>
    <sa-sheet-footer>
        <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-js-events-sheet" command="close">
            Close
        </sa-button>
        <sa-button variant="ButtonVariant.Outline" commandfor="--sheet-js-events-sheet" command="request-close">
            Request Close
        </sa-button>
    </sa-sheet-footer>
</sa-sheet>
<script>
    (function () {
        let counter = 0;
        const sheet = document.getElementById('--sheet-js-events-sheet');
        const output = document.getElementById('--sheet-js-events-output');
        const preventToggleCheckbox = document.getElementById('--sheet-js-events-prevent-toggle-checkbox');
        const preventCancelCheckbox = document.getElementById('--sheet-js-events-prevent-cancel-checkbox');

        sheet.addEventListener("beforetoggle", (e) => {
            if (preventToggleCheckbox.checked === true) {
                output.innerText = `${++counter}: beforetoggle event prevented!\n` + output.innerText;
                e.preventDefault();
                return;
            }
            output.innerText = `${++counter}: beforetoggle event: ${e.oldState} -> ${e.newState}\n` + output.innerText;
        });
        sheet.addEventListener("toggle", (e) => {
            output.innerText = `${++counter}: toggle event: ${e.oldState} -> ${e.newState}\n` + output.innerText;
        });
        sheet.addEventListener("cancel", (e) => {
            if (preventCancelCheckbox.checked === true) {
                output.innerText = `${++counter}: cancel event prevented!\n` + output.innerText;
                e.preventDefault();
                return;
            }
            output.innerText = `${++counter}: cancel event\n` + output.innerText;
        });
        sheet.addEventListener("close", (e) => {
            output.innerText = `${++counter}: close event\n` + output.innerText;
        });
    })();
</script>

Accessibility

<sa-sheet> renders a native <dialog> element that is opened with showModal(), so focus is trapped inside it, the rest of the page is inert, Escape closes it and focus returns to the trigger when it closes. The built-in close button has a screen-reader-only "Close" label.

The Tag Helper does not link the title to the sheet. Give <sa-sheet-title> an id and reference it from aria-labelledby on <sa-sheet> so the sheet is announced by its title, and do the same with aria-describedby and <sa-sheet-description> when there is one.

API Reference

<sa-sheet>

Renders a native <dialog> element styled to slide in from the edge of the screen. The sheet is opened and closed via the Invoker Commands API: a button with command="show-modal" or command="close" and a commandfor attribute referencing the sheet's id (auto-generated when you don't supply one).

The sheet is wrapped in a <sel-dialog> web component that locks page scrolling while it is open. See JavaScript & Interactivity.

Prop

Type

<sa-sheet-header>

Renders a <div> element that groups the sheet's title and description.

<sa-sheet-title>

Renders an <h2> element.

<sa-sheet-description>

Renders a <p> element.

Renders a <div> element pinned to the bottom of the sheet that hosts the sheet's action buttons.

On this page