Dialog
Overlaysimport { Dialog } from "bits-ui" Form dialog
A labelled modal form with controlled open state and predictable cancellation
<script lang="ts">
import { Dialog } from 'bits-ui';
import { X } from '@lucide/svelte';
let open = $state(false);
let projectName = $state('Atlas redesign');
let owner = $state('Mina Patel');
let savedProject = $state('');
function save(event: SubmitEvent) {
event.preventDefault();
savedProject = projectName;
open = false;
}
</script>
<div class="flex flex-col items-center gap-3">
<Dialog.Root bind:open>
<Dialog.Trigger class="btn btn-primary">Create project</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay class="fixed inset-0 z-[90] bg-ink-950/80 backdrop-blur-sm" />
<Dialog.Content
class="panel animate-pop-in fixed top-1/2 left-1/2 z-[100] w-[min(30rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 p-6 shadow-2xl outline-none"
>
<div class="pr-8">
<Dialog.Title class="text-lg font-semibold text-ink-50">Create a project</Dialog.Title>
<Dialog.Description class="mt-1 text-sm leading-6 text-ink-400">
Choose a clear name and assign the person accountable for delivery.
</Dialog.Description>
</div>
<form class="mt-5 space-y-4" onsubmit={save}>
<label class="block text-sm font-medium text-ink-200" for="dialog-project-name">
Project name
<input id="dialog-project-name" class="input mt-1.5 w-full" bind:value={projectName} required />
</label>
<label class="block text-sm font-medium text-ink-200" for="dialog-project-owner">
Owner
<input id="dialog-project-owner" class="input mt-1.5 w-full" bind:value={owner} required />
</label>
<div class="flex justify-end gap-2 pt-2">
<Dialog.Close type="button" class="btn btn-ghost">Cancel</Dialog.Close>
<button type="submit" class="btn btn-primary">Save project</button>
</div>
</form>
<Dialog.Close
class="btn btn-ghost btn-sm absolute top-4 right-4 size-8 p-0"
aria-label="Close dialog"
>
<X class="size-4" />
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
{#if savedProject}
<p class="text-xs text-ink-400">Saved <span class="text-ink-200">{savedProject}</span></p>
{/if}
</div>
Scrollable content
A bounded modal keeps its title and actions visible while the document body scrolls
<script lang="ts">
import { Dialog } from 'bits-ui';
import { X } from '@lucide/svelte';
const sections = [
['Scope', 'Design, implementation, accessibility review, and rollout support are included in this engagement.'],
['Delivery', 'Work is delivered in weekly increments. Each increment includes a review link and concise release notes.'],
['Ownership', 'Final source, design files, and documentation transfer to the client after the final invoice is settled.'],
['Confidentiality', 'Both parties agree to protect private product data and limit access to people working on the engagement.'],
['Termination', 'Either party may end the engagement with fourteen days written notice. Completed work remains billable.']
];
</script>
<Dialog.Root>
<Dialog.Trigger class="btn btn-primary">Review agreement</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay class="fixed inset-0 z-[90] bg-ink-950/80 backdrop-blur-sm" />
<Dialog.Content
class="panel animate-pop-in fixed top-1/2 left-1/2 z-[100] flex max-h-[min(38rem,calc(100vh-2rem))] w-[min(36rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden shadow-2xl outline-none"
>
<header class="border-b border-ink-800 px-6 py-5 pr-14">
<Dialog.Title class="text-lg font-semibold text-ink-50">Service agreement</Dialog.Title>
<Dialog.Description class="mt-1 text-sm text-ink-400">
Read all terms before accepting. The header and actions remain visible while the terms scroll.
</Dialog.Description>
</header>
<div class="min-h-0 flex-1 space-y-5 overflow-y-auto px-6 py-5">
{#each sections as section}
<section>
<h3 class="text-sm font-semibold text-ink-50">{section[0]}</h3>
<p class="mt-1 text-sm leading-6 text-ink-400">{section[1]}</p>
</section>
{/each}
</div>
<footer class="flex justify-end gap-2 border-t border-ink-800 px-6 py-4">
<Dialog.Close class="btn btn-ghost">Not now</Dialog.Close>
<Dialog.Close class="btn btn-primary">Accept terms</Dialog.Close>
</footer>
<Dialog.Close
class="btn btn-ghost btn-sm absolute top-4 right-4 size-8 p-0"
aria-label="Close dialog"
>
<X class="size-4" />
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
Dialog.Portal moves the overlay and content to document.body by
default, escaping local stacking contexts. Modal content traps focus, locks body scrolling, focuses
the first focusable control on open, and restores focus to the trigger on close. Keep Dialog.Title and Dialog.Description inside the content so their generated
IDs can label the dialog for assistive technology.
Escape and outside interaction close a dialog by default. Configure escapeKeydownBehavior or interactOutsideBehavior on Dialog.Content only when the workflow truly requires it; the corresponding event
callbacks can prevent a single dismissal. Set trapFocus={false} or preventScroll={false} only with an alternative accessibility strategy.