Feedback
Dialog
A second-layer modal. The overlay blurs the page while the panel uses a solid surface.
<script>
import { Dialog, Button } from "@codeatlasdev/ui";
let open = $state(false);
</script>
<Dialog bind:open title="Confirm" description="This cannot be undone.">
{#snippet actions()}
<Button variant="ghost" onclick={() => (open = false)}>Cancel</Button>
<Button variant="destructive" onclick={remove}>Delete</Button>
{/snippet}
</Dialog>
Built on the Bits UI Dialog: focus is trapped, Escape closes and the panel carries aria-modal. For decisions that need an explicit answer use AlertDialog. On phones, prefer Sheet for bottom-anchored content.
Confirmation
The destructive confirm and cancel pattern.
Code
<script lang="ts">
import { Dialog, Button } from '@codeatlasdev/ui';
import { Trash2 } from '@lucide/svelte';
let open = $state(false);
let confirmed = $state(false);
</script>
<Button variant="destructive" onclick={() => ((confirmed = false), (open = true))}>
{#snippet leading()}<Trash2 size={14} />{/snippet}
Delete item
</Button>
<Dialog
bind:open
title="Delete this item?"
description="This action cannot be undone. The item will be permanently removed from your workspace."
size="sm"
>
{#snippet actions()}
<Button variant="ghost" onclick={() => (open = false)}>Cancel</Button>
<Button variant="destructive" onclick={() => { confirmed = true; open = false; }}>Delete</Button>
{/snippet}
</Dialog>
{#if confirmed}<p class="feedback" aria-live="polite">Item deleted.</p>{/if}
<style>
.feedback { margin: 0; font-size: 13px; color: var(--ca-semantic-error); font-style: italic; }
</style>
Info
Informational content with a single action.
Code
<script lang="ts">
import { Dialog, Button } from '@codeatlasdev/ui';
import { Info } from '@lucide/svelte';
let open = $state(false);
</script>
<Button variant="outlined" onclick={() => (open = true)}>
{#snippet leading()}<Info size={14} />{/snippet}
View details
</Button>
<Dialog bind:open title="About this component">
<p class="body">
<strong>Dialog</strong> is built on the Bits UI <code>Dialog</code> primitive. It traps focus
(Tab cycles inside), closes on Escape and exposes correct <code>aria-modal</code> semantics.
</p>
<p class="body">
The overlay blurs the page while the panel uses a solid <code>--ca-surface-overlay</code>,
so there is no double blur over the scrim.
</p>
{#snippet actions()}
<Button variant="filled" onclick={() => (open = false)}>Got it</Button>
{/snippet}
</Dialog>
<style>
.body {
font-size: var(--ca-font-size-body2);
color: var(--ca-text-secondary);
line-height: 1.6;
margin: 0 0 10px;
}
.body:last-child { margin-bottom: 0; }
.body strong { color: var(--ca-text-primary); }
.body code {
font-family: var(--ca-font-mono);
font-size: 0.88em;
color: var(--ca-brand-text);
background: color-mix(in oklch, var(--ca-brand-main) 10%, transparent);
padding: 1px 5px;
border-radius: var(--ca-radius-xs);
}
</style>
A dialog holding a form, at the md size.
Code
<script lang="ts">
import { Dialog, Button, TextField, Select, Stack } from '@codeatlasdev/ui';
let open = $state(false);
let name = $state('');
let email = $state('');
let role = $state('viewer');
const roleOpts = [
{ value: 'admin', label: 'Admin' },
{ value: 'editor', label: 'Editor' },
{ value: 'viewer', label: 'Viewer' },
];
</script>
<Button variant="filled" onclick={() => (open = true)}>Invite member</Button>
<Dialog bind:open title="Invite team member" size="md">
<Stack gap={14}>
<TextField label="Full name" bind:value={name} placeholder="Ada Lovelace" />
<TextField label="Email" type="email" bind:value={email} placeholder="ada@company.com" />
<Select label="Role" bind:value={role} options={roleOpts} />
</Stack>
{#snippet actions()}
<Button variant="ghost" onclick={() => (open = false)}>Cancel</Button>
<Button variant="filled" disabled={!name || !email} onclick={() => (open = false)}>Send invite</Button>
{/snippet}
</Dialog>
Chromeless
No header, description or actions, and the body has no padding. The title becomes the visually hidden accessible name.
Code
<script lang="ts">
import { Dialog, Button } from '@codeatlasdev/ui';
let open = $state(false);
</script>
<Button variant="outlined" onclick={() => (open = true)}>Open chromeless</Button>
<Dialog bind:open title="Preview" size="md" chromeless>
<div class="media">
<p>Edge-to-edge content. Close with Esc or by clicking outside.</p>
<Button variant="filled" onclick={() => (open = false)}>Close</Button>
</div>
</Dialog>
<style>
.media {
display: grid;
gap: 12px;
padding: 24px;
justify-items: start;
color: var(--ca-text-secondary);
font-size: var(--ca-font-size-body2);
}
.media p { margin: 0; }
</style>
Split
size="split" is 1100 by 720 with two columns from the md breakpoint and a single column below it. Combine it with chromeless.
Code
<script lang="ts">
import { Dialog, Button, TextField, Stack } from '@codeatlasdev/ui';
let open = $state(false);
let name = $state('');
let email = $state('');
</script>
<Button variant="filled" onclick={() => (open = true)}>Open split</Button>
<Dialog bind:open title="Lead details" size="split" chromeless>
<div class="media">Media / summary</div>
<div class="form">
<Stack gap={14}>
<TextField label="Full name" bind:value={name} placeholder="Ada Lovelace" />
<TextField label="Email" type="email" bind:value={email} placeholder="ada@company.com" />
<Button variant="filled" onclick={() => (open = false)}>Save</Button>
</Stack>
</div>
</Dialog>
<style>
.media {
display: grid;
place-items: center;
min-height: 160px;
background: color-mix(in oklab, var(--ca-brand-main) 12%, var(--ca-surface-overlay));
color: var(--ca-text-secondary);
font-size: var(--ca-font-size-body2);
}
.form { padding: 24px; overflow-y: auto; }
</style>
Props
| Prop | Type | Default | Description |
|---|
open | boolean | false | Bindable open state. |
title | string | – | Header title. With chromeless it is the hidden accessible name. |
description | string | – | Subtitle below the title. |
size | 'sm' | 'md' | 'lg' | 'xl' | 'split' | 'md' | Width: 400, 480, 600 or 820px. split is 1100 by 720 in two columns from md. |
chromeless | boolean | false | Omits header, description and actions; the body has no padding. title and description stay available to screen readers. |
children | Snippet | – | Dialog body. |
actions | Snippet | – | Footer buttons. |
onOpenChange | (open: boolean) => void | – | Called whenever the open state changes. |
class | string | '' | Extra classes on the panel. |