# Sheet A dialog that slides in from an edge of the screen, for content that complements the page. ```ehtml Example import { Sheet, SheetBody, SheetFooter, SheetClose } from "@elements/ui/sheet"; import { Button } from "@elements/ui/button"; Edit profile save(form)}>Save changes Close ... ``` ## Parts | Part | What it is | |---|---| | `Sheet` | The root: owns open, renders the trigger slot and, while open, the `` overlay, the backdrop and the docked panel. | | `SheetHeader` | Stacks a title and description. | | `SheetTitle` | An ``; names the sheet (aria-labelledby). | | `SheetDescription` | A ``; describes the sheet (aria-describedby). | | `SheetBody` | The content between header and footer; it scrolls when the content is taller than the sheet, so the header and footer stay in place. | | `SheetFooter` | Actions pinned to the bottom, stacked full width. | | `SheetClose` | Wraps any element; clicking it closes this sheet. | ## Attributes ### Sheet | Attribute | Type | Default | Description | |---|---|---|---| | `open` | `boolean` (two-way) | `false` | Whether the sheet is open. Bind it to control it; the sheet writes false back when it closes itself. Omit it and the trigger slot opens it. | | `side` | `"top" \| "right" \| "bottom" \| "left" \| "start" \| "end"` | `"right"` | The edge the sheet docks to and slides from. start and end follow the writing direction (end is the left edge under rtl). | | `title` | `string` | `""` | Renders a SheetTitle in a header and names the sheet. | | `description` | `string` | `""` | Renders a SheetDescription under the title. | | `label` | `string` | `""` | aria-label for a sheet with no visible title. | | `modal` | `boolean \| "trap-focus"` | `true` | true: backdrop, inert page, focus trap, scroll lock. "trap-focus": focus is trapped but there is no backdrop or lock, and the page takes clicks. false: the page stays usable. | | `dismissOnEscape` | `boolean` | `true` | Escape closes the sheet. | | `dismissOnOutside` | `boolean` | `true` | A press outside the panel closes the sheet. | | `closeButton` | `boolean` | `true` | Shows the X button in the corner. | | `initialFocus` | `DialogFocus` | `""` | CSS selector of the element to focus on open, or a function of the interaction type (keyboard, mouse, touch, pen) that returns an element, a selector, true for the default or false to leave focus. Default: [autofocus], else the first focusable element that is not a link, else the panel. | | `returnFocus` | `boolean` | `true` | Return focus to the trigger (or whatever had focus) when the sheet closes. | | `finalFocus` | `DialogFocus` | `""` | CSS selector of the element to focus on close, in place of the trigger, or a function of the interaction type that returns an element, a selector, true for the trigger or false to leave focus. | | `size` | `"sm" \| "md" \| "lg" \| "xl" \| "full"` | `"md"` | Max width of a side sheet: 20, 24, 36, 48rem or the full viewport. A side sheet is 75% of the viewport up to that width; top and bottom sheets span the width. | | `scroll` | `"inside" \| "outside"` | `"inside"` | inside: the sheet fits its edge and SheetBody scrolls. outside: the sheet grows with its content and the overlay scrolls it as one (an outside scroll dialog); a side sheet is at least the viewport's height. | | `class` | `string` | `""` | Extra classes on the panel. | | `onOpenChangeComplete` | `(open: boolean) => void` | `() => {}` | Called after the enter animation (true) or once the sheet has left the page after its exit (false). | | `onOpenChange` | `(open: boolean, reason?: DialogChangeReason, details?: DialogChangeDetails) => void` | `() => {}` | Called when the sheet opens or closes itself, with a reason: trigger-press, outside-press, escape-key, close-press, focus-out, close-watcher (a platform close request such as Android back), or none when the browser closed it. Not called for changes the caller makes. The third argument is the event details: { reason, event (the DOM event behind the change, or null), trigger (the trigger element on trigger-press) }. | | `onInteractOutside` | `(e: Event) => void` | `() => {}` | Called before a press or focus outside dismisses it; e.preventDefault() keeps it open. | | `onOpenAutoFocus` | `(e: Event) => void` | `() => {}` | Called before focus moves in on open; e.preventDefault() leaves focus to the handler. | | `onCloseAutoFocus` | `(e: Event) => void` | `() => {}` | Called before focus returns on close; e.preventDefault() leaves focus to the handler. | | `onEscapeKeyDown` | `(e: KeyboardEvent) => void` | `() => {}` | Called on Escape before closing; e.preventDefault() keeps it open, and e.allowPropagation() lets the Escape reach the page's own key handlers too. | | `onPointerDownOutside` | `(e: PointerEvent) => void` | `() => {}` | Called on a press outside before closing; e.preventDefault() keeps it open. | ### SheetHeader, SheetTitle, SheetDescription, SheetBody, SheetFooter | Attribute | Type | Default | Description | |---|---|---|---| | `class` | `string` | `""` | Extra classes. | ## Slots | Slot | Content | |---|---| | `trigger` | The element that opens the sheet. It gets aria-haspopup, aria-expanded, aria-controls and data-state. | | `default` | The sheet's content, below the header. | ## Keyboard | Keys | Action | |---|---| | `Space / Enter` | On the trigger: opens the sheet. | | `Tab` | Moves to the next focusable element inside; wraps from the last to the first. | | `Shift + Tab` | Moves to the previous focusable element; wraps from the first to the last. | | `Escape` | Closes the sheet (unless dismissOnEscape is false or onEscapeKeyDown prevents it). | ## Data attributes | Attribute | Values | On | Description | |---|---|---|---| | `data-open` | present | .dialog-overlay, .sheet-backdrop, .sheet | While open; the same moment as data-state="open". | | `data-closed, data-ending-style` | present | .dialog-overlay, .sheet-backdrop, .sheet | During the exit animation; the same moment as data-state="closed". | | `data-starting-style` | present | .dialog-overlay, .sheet-backdrop, .sheet | For the first frame after the overlay mounts, so a CSS transition can start from it. | | `data-popup-open` | present | trigger | While open; the same moment as the trigger's data-state="open". | | `data-state` | `"open" \| "closed"` | trigger, .dialog-overlay, .sheet-backdrop, .sheet | closed during the exit animation. | | `data-side` | `"top" \| "right" \| "bottom" \| "left" \| "start" \| "end"` | .sheet | | | `data-modal` | `"true" \| "false"` | .dialog-overlay | | | `data-scroll-locked` | present | `` | While a modal sheet is open. | | `data-sheet-scroll-stop` | present | .sheet-body | The body overflows and is a Tab stop (tabindex 0), a region named after the title. | | `data-nested` | present | .sheet | The sheet sits inside another dialog panel. | | `data-nested-dialog-open` | present | .sheet | A dialog opened from inside this one is open. --nested-dialogs holds the count. | ## Accessibility - The overlay is a native `` opened with showModal(): role dialog, the top layer, and an inert page behind it. - aria-modal="true", aria-labelledby (the title) and aria-describedby (the description) are set on it. - Focus moves into the sheet on open, is trapped while modal, and returns to the trigger on close. - The close button is an icon button with aria-label="Close". ## See also - [Button](button): A button or a link that looks like one, in six variants and four sizes. - [AlertDialog](alert-dialog): A modal that interrupts with something important and waits for an answer. - [Drawer](drawer): A panel that slides from an edge and can be dragged away, with snap points and a scaled background.
`; describes the sheet (aria-describedby). | | `SheetBody` | The content between header and footer; it scrolls when the content is taller than the sheet, so the header and footer stay in place. | | `SheetFooter` | Actions pinned to the bottom, stacked full width. | | `SheetClose` | Wraps any element; clicking it closes this sheet. | ## Attributes ### Sheet | Attribute | Type | Default | Description | |---|---|---|---| | `open` | `boolean` (two-way) | `false` | Whether the sheet is open. Bind it to control it; the sheet writes false back when it closes itself. Omit it and the trigger slot opens it. | | `side` | `"top" \| "right" \| "bottom" \| "left" \| "start" \| "end"` | `"right"` | The edge the sheet docks to and slides from. start and end follow the writing direction (end is the left edge under rtl). | | `title` | `string` | `""` | Renders a SheetTitle in a header and names the sheet. | | `description` | `string` | `""` | Renders a SheetDescription under the title. | | `label` | `string` | `""` | aria-label for a sheet with no visible title. | | `modal` | `boolean \| "trap-focus"` | `true` | true: backdrop, inert page, focus trap, scroll lock. "trap-focus": focus is trapped but there is no backdrop or lock, and the page takes clicks. false: the page stays usable. | | `dismissOnEscape` | `boolean` | `true` | Escape closes the sheet. | | `dismissOnOutside` | `boolean` | `true` | A press outside the panel closes the sheet. | | `closeButton` | `boolean` | `true` | Shows the X button in the corner. | | `initialFocus` | `DialogFocus` | `""` | CSS selector of the element to focus on open, or a function of the interaction type (keyboard, mouse, touch, pen) that returns an element, a selector, true for the default or false to leave focus. Default: [autofocus], else the first focusable element that is not a link, else the panel. | | `returnFocus` | `boolean` | `true` | Return focus to the trigger (or whatever had focus) when the sheet closes. | | `finalFocus` | `DialogFocus` | `""` | CSS selector of the element to focus on close, in place of the trigger, or a function of the interaction type that returns an element, a selector, true for the trigger or false to leave focus. | | `size` | `"sm" \| "md" \| "lg" \| "xl" \| "full"` | `"md"` | Max width of a side sheet: 20, 24, 36, 48rem or the full viewport. A side sheet is 75% of the viewport up to that width; top and bottom sheets span the width. | | `scroll` | `"inside" \| "outside"` | `"inside"` | inside: the sheet fits its edge and SheetBody scrolls. outside: the sheet grows with its content and the overlay scrolls it as one (an outside scroll dialog); a side sheet is at least the viewport's height. | | `class` | `string` | `""` | Extra classes on the panel. | | `onOpenChangeComplete` | `(open: boolean) => void` | `() => {}` | Called after the enter animation (true) or once the sheet has left the page after its exit (false). | | `onOpenChange` | `(open: boolean, reason?: DialogChangeReason, details?: DialogChangeDetails) => void` | `() => {}` | Called when the sheet opens or closes itself, with a reason: trigger-press, outside-press, escape-key, close-press, focus-out, close-watcher (a platform close request such as Android back), or none when the browser closed it. Not called for changes the caller makes. The third argument is the event details: { reason, event (the DOM event behind the change, or null), trigger (the trigger element on trigger-press) }. | | `onInteractOutside` | `(e: Event) => void` | `() => {}` | Called before a press or focus outside dismisses it; e.preventDefault() keeps it open. | | `onOpenAutoFocus` | `(e: Event) => void` | `() => {}` | Called before focus moves in on open; e.preventDefault() leaves focus to the handler. | | `onCloseAutoFocus` | `(e: Event) => void` | `() => {}` | Called before focus returns on close; e.preventDefault() leaves focus to the handler. | | `onEscapeKeyDown` | `(e: KeyboardEvent) => void` | `() => {}` | Called on Escape before closing; e.preventDefault() keeps it open, and e.allowPropagation() lets the Escape reach the page's own key handlers too. | | `onPointerDownOutside` | `(e: PointerEvent) => void` | `() => {}` | Called on a press outside before closing; e.preventDefault() keeps it open. | ### SheetHeader, SheetTitle, SheetDescription, SheetBody, SheetFooter | Attribute | Type | Default | Description | |---|---|---|---| | `class` | `string` | `""` | Extra classes. | ## Slots | Slot | Content | |---|---| | `trigger` | The element that opens the sheet. It gets aria-haspopup, aria-expanded, aria-controls and data-state. | | `default` | The sheet's content, below the header. | ## Keyboard | Keys | Action | |---|---| | `Space / Enter` | On the trigger: opens the sheet. | | `Tab` | Moves to the next focusable element inside; wraps from the last to the first. | | `Shift + Tab` | Moves to the previous focusable element; wraps from the first to the last. | | `Escape` | Closes the sheet (unless dismissOnEscape is false or onEscapeKeyDown prevents it). | ## Data attributes | Attribute | Values | On | Description | |---|---|---|---| | `data-open` | present | .dialog-overlay, .sheet-backdrop, .sheet | While open; the same moment as data-state="open". | | `data-closed, data-ending-style` | present | .dialog-overlay, .sheet-backdrop, .sheet | During the exit animation; the same moment as data-state="closed". | | `data-starting-style` | present | .dialog-overlay, .sheet-backdrop, .sheet | For the first frame after the overlay mounts, so a CSS transition can start from it. | | `data-popup-open` | present | trigger | While open; the same moment as the trigger's data-state="open". | | `data-state` | `"open" \| "closed"` | trigger, .dialog-overlay, .sheet-backdrop, .sheet | closed during the exit animation. | | `data-side` | `"top" \| "right" \| "bottom" \| "left" \| "start" \| "end"` | .sheet | | | `data-modal` | `"true" \| "false"` | .dialog-overlay | | | `data-scroll-locked` | present | `