Skip to content
CodeAtlas UI

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
svelte
<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
svelte
<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>

Form

A dialog holding a form, at the md size.

Code
svelte
<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
svelte
<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
svelte
<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

PropTypeDefaultDescription
openbooleanfalseBindable open state.
titlestring–Header title. With chromeless it is the hidden accessible name.
descriptionstring–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.
chromelessbooleanfalseOmits header, description and actions; the body has no padding. title and description stay available to screen readers.
childrenSnippet–Dialog body.
actionsSnippet–Footer buttons.
onOpenChange(open: boolean) => void–Called whenever the open state changes.
classstring''Extra classes on the panel.