Dialog
A modal panel with a title, description, body and free-form footer — portals out, traps focus, and locks the page's scroll while open
Preview
<script lang="ts">
import { Dialog } from "fancy-ui-svelte";
import { Button } from "fancy-ui-svelte";
let open = $state(false);
let email = $state("");
</script>
{#snippet trigger()}
<Button>Invite member</Button>
{/snippet}
{#snippet footer()}
<Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
<Button onclick={() => (open = false)}>Invite</Button>
{/snippet}
<Dialog
bind:open
{trigger}
{footer}
title="Invite a member"
description="Send an email invite to join the workspace."
>
<input
type="email"
bind:value={email}
placeholder="email@example.com"
aria-label="Email address"
class="border-input bg-background text-muted-foreground placeholder:text-muted-foreground focus-visible:ring-ring/50 focus-visible:border-ring w-full rounded-lg border px-3 py-2 text-[12px] outline-none focus-visible:ring-2"
/>
</Dialog>Installation
Usage
<script lang="ts">
import { Dialog } from 'fancy-ui-svelte';
</script>
<Dialog />Examples
Basic Usage
A trigger, title, description, body and footer.
<script lang="ts">
import { Dialog } from "$lib/fancy-ui/dialog";
import { Button } from "$lib/fancy-ui/button";
let open = $state(false);
let email = $state("");
</script>
{#snippet trigger()}
<Button>Invite member</Button>
{/snippet}
{#snippet footer()}
<Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
<Button onclick={() => (open = false)}>Invite</Button>
{/snippet}
<Dialog
bind:open
{trigger}
{footer}
title="Invite a member"
description="Send an email invite to join the workspace."
>
<input
type="email"
bind:value={email}
placeholder="email@example.com"
aria-label="Email address"
class="border-input bg-background text-muted-foreground placeholder:text-muted-foreground focus-visible:ring-ring/50 focus-visible:border-ring w-full rounded-lg border px-3 py-2 text-[12px] outline-none focus-visible:ring-2"
/>
</Dialog>Controlled Open
Non-bound open plus onOpenChange, no trigger snippet.
<script lang="ts">
import { Dialog } from "$lib/fancy-ui/dialog";
import { Button } from "$lib/fancy-ui/button";
// `open` is a plain, non-bound value here — the parent owns the state and
// only ever learns about changes through `onOpenChange`, the same pattern
// a caller reaches for when open/close is driven by something other than
// this exact button (a route change, a keyboard shortcut, a wizard step).
let isOpen = $state(false);
</script>
<Button onclick={() => (isOpen = true)}>Open dialog</Button>
<Dialog
open={isOpen}
onOpenChange={(next) => (isOpen = next)}
title="Restart required"
description="Applying this setting restarts the current session."
>
{#snippet footer()}
<Button variant="outline" onclick={() => (isOpen = false)}>Not now</Button>
<Button onclick={() => (isOpen = false)}>Restart</Button>
{/snippet}
</Dialog>Not Dismissible
dismissible={false} — Escape and outside click both disabled.
<script lang="ts">
import { Dialog } from "$lib/fancy-ui/dialog";
import { Button } from "$lib/fancy-ui/button";
let open = $state(false);
</script>
{#snippet trigger()}
<Button variant="outline">Start setup</Button>
{/snippet}
<!--
dismissible={false} turns off Escape and outside click — for a step that
should not be lost to a stray click. The ✕ button is unaffected: it is
an explicit action, not the accidental dismiss this prop guards against,
so it stays the one deliberate way out.
-->
<Dialog
bind:open
{trigger}
dismissible={false}
title="Finish setting up your workspace"
description="This only takes a moment. Escape and clicking outside are disabled here — use the buttons below or the close icon."
>
{#snippet footer()}
<Button onclick={() => (open = false)}>Done</Button>
{/snippet}
</Dialog>Custom Initial Focus
Sends focus to a specific field instead of the default close button.
<script lang="ts">
import { Dialog } from "$lib/fancy-ui/dialog";
import { Button } from "$lib/fancy-ui/button";
let open = $state(false);
let name = $state("Untitled project");
let nameInput: HTMLInputElement | null = $state(null);
</script>
{#snippet trigger()}
<Button variant="outline">Rename project</Button>
{/snippet}
<!--
Without initialFocus, the first focusable descendant would be the ✕ close
button — a reasonable default, but not what a rename dialog wants. Passing
the input's own element sends focus straight to it instead.
-->
<Dialog bind:open initialFocus={nameInput} {trigger} title="Rename project">
<input
bind:this={nameInput}
bind:value={name}
type="text"
class="border-input bg-background focus-visible:ring-ring/50 focus-visible:border-ring w-full rounded-lg border px-3 py-2 text-[13px] outline-none focus-visible:ring-2"
/>
{#snippet footer()}
<Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
<Button onclick={() => (open = false)}>Save</Button>
{/snippet}
</Dialog>Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | false | Whether the dialog is open; bindable |
onOpenChange | (open: boolean) => void | - | Fires whenever open changes, from any trigger |
title | string | - | The heading. Omitted entirely (not just visually) when not given |
description | string | - | The copy under the title. Same omission rule as title |
dismissible | boolean | true | Whether Escape and an outside click close the dialog; the close button always works |
initialFocus | HTMLElement | null | - | Element to focus once the dialog opens; defaults to the first focusable descendant |
class | string | - | Additional CSS classes for the panel |
ref | HTMLDivElement | null | null | Bindable reference to the panel element |
Slots
| Slot | Description |
|---|---|
children | The dialog's body |
footer | The action row under the body — free-form, callers build their own buttons |
trigger | Optional trigger; renders in place and opens the dialog on activation |
Links
Related components
AlertDialog
A confirmation dialog for consequential, hard-to-undo actions — fixed confirm/cancel actions, never dismissed by an outside click
Drawer
A modal bottom sheet with a grab handle, draggable shut with a downward swipe — for filters, quick actions, or any touch-first task
HoverCard
An anchored panel that opens on hover or focus with a short delay, for richer previews than a tooltip — a profile card, a link preview