Skip to main content
Urbicon UI

Dialog

A modal overlay for a task the user must complete or dismiss before returning to the page. Built on the native <dialog> element.

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, Dialog renders your content straight into the panel with no header or close button, so you wire the actions yourself. Reach for it in short confirmation flows.
<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 switch on the structured layout: a header with a built-in close button, a scrollable body, and a 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, results don't shift below the fold as the user types.
<Dialog bind:open placement="top" size="md">
  <input type="text" placeholder="Search..." />
  ...results...
</Dialog>

Scrollable Content

Long body content scrolls within the panel while the header and footer stay pinned. Reach for it 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

Neon terminal

Restyle the panel with slotClasses. It keeps the dialog's radius tier, centering, focus trap and scroll lock. Only the fill, border and neon glow are raw (a terminal green the token palette has no equivalent for).
<Dialog
  bind:open
  slotClasses={{
    panel: 'border-green-500/30 bg-black text-green-400 shadow-[0_0_30px_rgba(34,197,94,0.15)]',
    content: 'font-mono text-xs'
  }}
>
  ...
</Dialog>

This is one of five ways to restyle a block. See Customization for class, slotClasses, unstyled, preset and provider-level overrides.

03 Accessibility

Native Dialog

Built on the native <dialog> element opened with showModal(), so the page behind it goes inert. A title becomes the dialog's accessible name through aria-labelledby.

Focus Trap

On open, focus moves to the first interactive element inside the panel. While open, focus is trapped: Tab cycles the interactive elements and never leaves. On close, focus returns to the element that opened the dialog.

Dismissal

Escape closes the dialog (closeOnEscape), and so does a click on the backdrop (closeOnBackdropClick). Both are on by default. onClose fires on any dismissal, so reset your state there.

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';