Skip to main content
Urbicon UI

Dialog

Overlay dialog with optional structured layout (title/footer/intent), focus trapping, and keyboard management. Built on native <dialog>.

Playground

Placement
<script lang="ts">
  import { Dialog } from '@urbicon-ui/blocks';

  let open = $state(false);
</script>

<Dialog
  bind:open
  title="Delete project"
>
  <p class="text-text-secondary text-sm">
    {#if values.title}
      This is the dialog body. Structured layout with header, body, and footer.
    {:else}
      This is a content-agnostic overlay. Adjust the controls to see how size, placement, and
      dismissal behavior change.
    {/if}
  </p>
  {#if values.title}
    {#snippet footer()}
      <div class="flex justify-end gap-2">
        <Button variant="ghost" onclick={() => (open = false)}>Cancel</Button>
        <Button onclick={() => (open = false)}>Confirm</Button>
      </div>
    {/snippet}
  {:else}
    <div class="mt-4 flex justify-end gap-2">
      <Button variant="ghost" onclick={() => (open = false)}>Cancel</Button>
      <Button onclick={() => (open = false)}>Confirm</Button>
    </div>
  {/if}
</Dialog>

01 Examples

Confirmation (content-only)

Without a title prop, Dialog renders your content directly — ideal for short confirmation flows with full layout freedom.
<Dialog bind:open>
  <p class="text-text-primary text-sm font-medium">Delete this item?</p>
  <p class="text-text-tertiary mt-1 text-sm">This action cannot be undone.</p>
  <div class="flex justify-end gap-2 mt-4">
    <Button variant="ghost" onclick={() => open = false}>Cancel</Button>
    <Button intent="danger" onclick={() => open = false}>Delete</Button>
  </div>
</Dialog>

Form Dialog

Pass a title to enable the structured layout with header, scrollable body, and footer — the standard pattern for forms and editors.
<Dialog bind:open title="Create Account" size="md">
  <form>
    <Input label="Name" bind:value={name} />
    <Input label="Email" bind:value={email} />
  </form>
  {#snippet footer()}
    <Button variant="ghost" onclick={() => open = false}>Cancel</Button>
    <Button type="submit">Create</Button>
  {/snippet}
</Dialog>

Top Placement (command palette)

Use placement='top' for search or command-palette flows — anchored near the top of the viewport so results don't shift below the fold as users type.
<Dialog bind:open placement="top" size="md">
  <input type="text" placeholder="Search..." />
  ...results...
</Dialog>

Scrollable Content

Long body content scrolls automatically while header and footer stay pinned — useful for terms, changelogs, or any read-heavy dialog.
<Dialog bind:open title="Terms of Service" size="lg">
  <div class="space-y-4"><p>Long content here...</p></div>
  {#snippet footer()}
    <Button variant="ghost" onclick={() => open = false}>Decline</Button>
    <Button onclick={() => open = false}>Accept</Button>
  {/snippet}
</Dialog>

02 Customization

Slot Class Overrides

Fine-tune individual slots without going fully unstyled.
<Dialog
  bind:open
  title="Quick Settings"
  slotClasses={{
    panel: 'rounded-2xl',
    header: 'bg-surface-subtle',
    body: 'bg-surface-base',
    footer: 'bg-surface-subtle'
  }}
>
  ...
</Dialog>

Fully Custom (unstyled)

Strip defaults with unstyled and rebuild the look entirely via slotClasses.
<Dialog
  unstyled
  bind:open
  slotClasses={{
    dialog: 'fixed inset-0 z-[var(--z-modal)] flex items-center justify-center p-4',
    backdrop: 'fixed inset-0 bg-black/80',
    panel: 'relative w-full max-w-xs border border-green-500/30 bg-black text-green-400',
    content: 'p-5 font-mono text-xs'
  }}
>
  ...
</Dialog>

A dialog chrome shared across the app belongs in presets.Dialog on BlocksProvider — and because ConfirmDialog forwards its styling props to the inner Dialog, the same preset covers both components. See Customization.

03 Accessibility

Native Dialog

Built on <dialog> with showModal() for native inertness and stacking context. Screen readers announce it automatically via aria-modal="true". When a title is set, it is linked via aria-labelledby.

Focus Trap

When open, focus is trapped inside the dialog. Tab cycles through interactive elements. On close, focus returns to the element that opened the dialog.

Keyboard

Escape closes the dialog (configurable via closeOnEscape).

Scroll Lock

While open, body scroll is locked. Long dialog content scrolls within the panel itself.

04 API Reference

20 props
20 props 1 required
Prop
Type
Default
Description

05 Types

Local type definitions used by this component.

6 types
Name
Kind
Category
Used by
Description

06 Installation

Import

import { Dialog } from '@urbicon-ui/blocks';