You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5.7 KiB
5.7 KiB
Dialog
A modal or non-modal window overlaid on the primary content. Supports focus trapping, scroll lock, nested dialogs, and an alertdialog variant.
Anatomy
<Dialog.Provider bind:open>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Overlay />
<Dialog.Content>
<Dialog.Title>Title</Dialog.Title>
<Dialog.Description>Description text.</Dialog.Description>
<Dialog.Close>Close</Dialog.Close>
</Dialog.Content>
</Dialog.Provider>
Content and Overlay render where placed. Wrap in a Portal component for body-level rendering if needed for z-index stacking.
Parts
| Part | Element | Description |
|---|---|---|
Provider |
none | Root context. Manages open state, no DOM of its own. |
Trigger |
<button> |
Opens the dialog on click. |
Content |
<div> |
Dialog container. Focus trap, scroll lock, dismissal layers integrated. |
Overlay |
<div> |
Backdrop behind the dialog. |
Title |
<div> |
Dialog heading. Linked via aria-labelledby. |
Description |
<div> |
Dialog description. Linked via aria-describedby. |
Close |
<button> |
Closes the dialog on click. |
ARIA
| Part | Attribute | Value |
|---|---|---|
| Trigger | aria-haspopup |
dialog |
| Trigger | aria-expanded |
true | false |
| Trigger | aria-controls |
ID of Content |
| Trigger | aria-label |
Translated label |
| Content | role |
dialog | alertdialog |
| Content | aria-modal |
true |
| Content | aria-labelledby |
ID of Title |
| Content | aria-describedby |
ID of Description |
| Content | aria-roledescription |
Optional custom description |
| Overlay | aria-hidden |
true |
| Title | role |
heading |
| Title | aria-level |
Number (default 2) |
| Close | aria-label |
Translated label |
Data Attributes
| Part | Attribute | Values |
|---|---|---|
| Trigger | data-dialog-trigger |
Always present |
| Trigger | data-state |
open | closed |
| Content | data-dialog-content |
Always present |
| Content | data-state |
open | closed |
| Content | data-nested |
Present when this is a nested dialog |
| Content | data-nested-open |
Present when a child dialog is open |
| Content | data-starting-style |
Present during open animation (1 frame) |
| Content | data-ending-style |
Present during close animation |
| Overlay | data-dialog-overlay |
Always present |
| Overlay | data-state |
open | closed |
| Overlay | data-nested |
Present when nested |
| Overlay | data-nested-open |
Present when a child dialog is open |
| Overlay | data-starting-style |
Present during open animation |
| Overlay | data-ending-style |
Present during close animation |
CSS Variables
| Variable | Part | Description |
|---|---|---|
--soma-dialog-depth |
Content, Overlay | Nesting depth (0 for first dialog) |
--soma-dialog-nested-count |
Content, Overlay | Number of nested dialogs open |
Keyboard
| Key | Action |
|---|---|
Escape |
Close the dialog (configurable via escapeKeydownBehavior) |
Tab |
Cycle focus within the dialog (focus is trapped) |
Shift+Tab |
Cycle focus backwards within the dialog |
Focus is automatically moved to the first focusable element on open. On close, focus returns to the trigger.
Usage
Basic modal
<script>
import { Dialog } from '$soma/components';
let open = $state(false);
</script>
<Dialog.Provider bind:open>
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
<Dialog.Overlay />
<Dialog.Content>
<Dialog.Title>Confirm Action</Dialog.Title>
<Dialog.Description>Are you sure?</Dialog.Description>
<Dialog.Close>Cancel</Dialog.Close>
</Dialog.Content>
</Dialog.Provider>
Alert dialog
<Dialog.Provider bind:open variant="alertdialog">
<!-- role="alertdialog" on Content, cannot dismiss by clicking outside -->
</Dialog.Provider>
Non-modal
<Dialog.Provider bind:open modal={false}>
<!-- No overlay, no focus trap, no scroll lock -->
</Dialog.Provider>
Nested dialogs
<Dialog.Provider bind:open={outer}>
<Dialog.Content>
<Dialog.Provider bind:open={inner}>
<Dialog.Trigger>Open Nested</Dialog.Trigger>
<Dialog.Content>
<!-- data-nested, --soma-dialog-depth: 1 -->
</Dialog.Content>
</Dialog.Provider>
</Dialog.Content>
</Dialog.Provider>