Dialog
A focused panel with a title, Escape, and focus restoration.
Try Tab, Escape, and clicking outside.
More examples
Icon trigger
An icon-only trigger takes its name from triggerLabel.
Source
<script lang="ts">
import 'portal-bits/styles.css';
import 'portal-bits/tokens.css';
import type { Snippet } from 'svelte';
let { children }: { children: Snippet } = $props();
</script>
{@render children()}
<script lang="ts">
import DialogExample from './DialogExample.svelte';
</script>
<DialogExample description={true} />
<script lang="ts">
import { Alert, Button, Dialog, Input } from 'portal-bits';
let { description = true }: { description?: boolean } = $props();
let open = $state(false);
let name = $state('Alex Morgan');
let saved = $state(false);
let error = $state('');
function save(event: SubmitEvent) {
event.preventDefault();
if (!name.trim()) {
error = 'Enter a display name.';
return;
}
error = '';
saved = true;
open = false;
}
</script>
<div class="stack">
<Dialog
title="Make yourself at home"
description={description ? 'Edit your display name. This example stays in your browser.' : ''}
bind:open
>
{#snippet trigger()}
Edit profile
<svg
width="15"
height="15"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"><path d="M7 7h10v10" /><path d="M7 17 17 7" /></svg
>
{/snippet}
<form onsubmit={save}>
<Input
label="Display name"
bind:value={name}
invalid={!!error}
aria-describedby={error ? 'name-error' : undefined}
/>
{#if error}<Alert id="name-error" message={error} />{/if}
<div class="actions">
<span>Local demo only</span>
<Button variant="green" type="submit">Save profile</Button>
</div>
</form>
</Dialog>
<p aria-live="polite">
{saved ? `Profile saved as ${name}.` : 'Try Tab, Escape, and clicking outside.'}
</p>
</div>
<style>
.stack {
display: grid;
justify-items: center;
gap: 12px;
}
p {
margin: 0;
}
.actions {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 24px;
color: var(--portal-muted);
font-size: 14px;
}
</style>
API reference
These tables list the props and snippets that Dialog declares. Attributes passed through to an element or primitive are listed under Forwarded attributes, not here.
Props
| Name | Type | Default | Required | Binding | Description |
|---|---|---|---|---|---|
open | boolean | false | No | Bindable | Whether the dialog is open. |
title | string | None | Yes | One-way | The dialog heading. It names the dialog. |
description | string | "" | No | One-way | Supporting text under the heading. It is omitted when empty. |
triggerLabel | string | None | No | One-way | Sets `aria-label` on the trigger. Use it when the trigger content is an icon. |
triggerClass | string | Secondary 40px button | No | One-way | Replaces the trigger classes. The default is a secondary 40px button. |
theme | 'light' | 'dark' | light | No | One-way | Theme scope for the portaled content. Portaled content does not inherit the page scope. |
Snippets
| Name | Type | Default | Required | Binding | Description |
|---|---|---|---|---|---|
trigger | Snippet | None | No | One-way | The trigger content. Without it, no trigger renders; bind `open`. |
children | Snippet | None | No | One-way | The dialog body. |
Forwarded attributes
None. Dialog forwards no attributes.
The primitive underneath is a Bits UI peer dependency. Its own options are documented in the Bits UI Dialog documentation. Only the props listed on this page are part of the Dialog contract. No data attributes are documented as stable.
Limits
- Only `open` of Bits UI `Dialog.Root` is wrapped. Its other options and callbacks are not exposed.
- The close button is always present and named "Close dialog".
- `triggerLabel` replaces the visible trigger text as the accessible name.