component

Dialog

A flexible dialog component for displaying modal content, overlays, and drawer interfaces. Supports both modal and non-modal modes, drawer positioning, scroll locking, and stacked dialogs with cascade animations.

Default

A centered modal dialog with title, content, and a close button.

Dialog Title

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ex placeat unde natus ut ea voluptatum vel officiis commodi optio a totam repellendus, fuga, laborum voluptatibus recusandae eos. Voluptate, vel non!
---
import Dialog from "../dialog.astro";
import Button from "@pindoba/astro-button";
import Stamp from "@pindoba/astro-stamp";
import { Info } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "md", direction: "column", align: "start" })}>
  <Dialog id="astro-demo-dialog-default" title="Dialog Title">
    <Stamp
      slot="heading-leading"
      size="sm"
      shape="circle"
      feedback="primary"
      emphasis="secondary"
    >
      <Info />
    </Stamp>
    Lorem ipsum dolor sit amet consectetur adipisicing elit. Ex placeat unde natus
    ut ea voluptatum vel officiis commodi optio a totam repellendus, fuga, laborum
    voluptatibus recusandae eos. Voluptate, vel non!
  </Dialog>

  <Button id="astro-demo-dialog-trigger">Open Dialog</Button>
</div>

<script>
  document
    .getElementById("astro-demo-dialog-trigger")
    ?.addEventListener("click", () => {
      window.__PindobaDialogManager?.open("astro-demo-dialog-default");
    });
</script>

The dialog header renders a Banner internally, so every Banner slot is available directly on Dialog. Use headingLeading / headingTrailing (inline with the title), subheadingLeading / subheadingTrailing (inline with the subtitle), and Banner’s outer leading / trailing slots for content that sits beside the whole heading group — the latter is the right place for header actions now that Dialog no longer defines its own headerActions slot.

Scheduled Maintenance

System will be unavailable on March 15, 2026

All services will be offline from 02:00 to 04:00 UTC. No action is required — your data will remain intact throughout the maintenance window.

File Manager

12 files

Browse and manage your project files

Select a file to preview, rename, move, or remove it from your project workspace.

Account Settings

Manage your preferences and security

Verified
Update your display name, email address, and two-factor authentication settings from this panel.

Notifications

3

3 unread messages

You have new notifications about team activity, mentions, and system updates. Review them here or mark everything as read.

Filters

Choose the filters you want to apply to the current view. Changes take effect immediately once applied.
---
import Dialog from "../dialog.astro";
import Button from "@pindoba/astro-button";
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import { Bell, Folder, Funnel, User } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "md", direction: "row", flexWrap: "wrap" })}>
  <!-- Subtitle only -->
  <Dialog
    id="astro-demo-dialog-header-notice"
    title="Scheduled Maintenance"
    subtitle="System will be unavailable on March 15, 2026"
  >
    All services will be offline from 02:00 to 04:00 UTC. No action is required
    — your data will remain intact throughout the maintenance window.
  </Dialog>

  <Button id="astro-demo-dialog-header-notice-trigger">With Subtitle</Button>

  <!-- Stamp leading + Badge trailing -->
  <Dialog
    id="astro-demo-dialog-header-files"
    title="File Manager"
    subtitle="Browse and manage your project files"
  >
    <Stamp
      slot="heading-leading"
      shape="square"
      background="surface.ground"
      size="sm"
    >
      <Folder />
    </Stamp>
    <Badge slot="heading-trailing" size="sm">12 files</Badge>
    Select a file to preview, rename, move, or remove it from your project workspace.
  </Dialog>

  <Button id="astro-demo-dialog-header-files-trigger">Stamp + Badge</Button>

  <!-- Stamp in heading + subheading leading, Badge in subheading trailing -->
  <Dialog
    id="astro-demo-dialog-header-account"
    title="Account Settings"
    subtitle="Manage your preferences and security"
  >
    <Stamp
      slot="heading-leading"
      feedback="primary"
      emphasis="secondary"
      shape="circle"
      size="sm"
    >
      <User />
    </Stamp>
    <Badge slot="subheading-trailing" size="sm" feedback="success"
      >Verified</Badge
    >
    Update your display name, email address, and two-factor authentication settings
    from this panel.
  </Dialog>

  <Button id="astro-demo-dialog-header-account-trigger">Subheading Slots</Button
  >

  <!-- Banner trailing slot as a header action (no dedicated dialog slot) -->
  <Dialog
    id="astro-demo-dialog-header-notifications"
    title="Notifications"
    subtitle="3 unread messages"
  >
    <Stamp
      slot="heading-leading"
      feedback="warning"
      emphasis="secondary"
      shape="circle"
      size="sm"
    >
      <Bell />
    </Stamp>
    <Badge slot="heading-trailing" size="sm" feedback="warning">3</Badge>
    <Button slot="trailing" size="sm" emphasis="ghost">Mark all read</Button>
    You have new notifications about team activity, mentions, and system updates.
    Review them here or mark everything as read.
  </Dialog>

  <Button id="astro-demo-dialog-header-notifications-trigger"
    >Trailing Action</Button
  >

  <!-- Stamp + actions, no subtitle -->
  <Dialog id="astro-demo-dialog-header-filters" title="Filters">
    <Stamp
      slot="heading-leading"
      feedback="primary"
      emphasis="secondary"
      shape="square"
      size="sm"
    >
      <Funnel />
    </Stamp>
    <div slot="trailing" class={stack({ direction: "row", gap: "2xs" })}>
      <Button size="sm" emphasis="ghost">Reset</Button>
      <Button size="sm" emphasis="secondary">Apply</Button>
    </div>
    Choose the filters you want to apply to the current view. Changes take effect
    immediately once applied.
  </Dialog>

  <Button id="astro-demo-dialog-header-filters-trigger"
    >Actions, No Subtitle</Button
  >
</div>

<script>
  const ids = [
    "astro-demo-dialog-header-notice",
    "astro-demo-dialog-header-files",
    "astro-demo-dialog-header-account",
    "astro-demo-dialog-header-notifications",
    "astro-demo-dialog-header-filters",
  ];
  ids.forEach((id) => {
    document.getElementById(`${id}-trigger`)?.addEventListener("click", () => {
      window.__PindobaDialogManager?.open(id);
    });
  });
</script>

Custom header

For headers that aren’t a title/subtitle — a search field, a segmented control, a stepper — the header snippet (Astro: header slot) replaces the Banner with your own content while keeping the header row’s styling and the close button. It renders even when no title/subtitle is set, which makes it the foundation for command-palette-style dialogs.

---
import Dialog from "../dialog.astro";
import Button from "@pindoba/astro-button";
import { InputRoot, InputField } from "@pindoba/astro-input";
import Affix from "@pindoba/astro-affix";
import Stamp from "@pindoba/astro-stamp";
import { Search } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";

const commands = [
  "Open file…",
  "Go to line",
  "Toggle terminal",
  "Format document",
  "Rename symbol",
  "Find in files",
];
---

<div class={stack({ gap: "md", direction: "row", flexWrap: "wrap" })}>
  {
    /* `header` slot replaces the Banner with a search input (command-palette preview) */
  }
  <Dialog
    id="astro-demo-dialog-header-input"
    showCloseButton={false}
    passThrough={{ header: { style: { p: "xs" } } }}
  >
    <InputRoot slot="header" fullWidth>
      <Affix decorative><Stamp emphasis="ghost"><Search /></Stamp></Affix>
      <InputField type="search" placeholder="Type a command…" />
    </InputRoot>

    <div class={stack({ gap: "3xs", p: "xs" })}>
      {
        commands.map((command) => (
          <Button
            emphasis="ghost"
            passThrough={{
              root: { style: { width: "100%", justifyContent: "flex-start" } },
            }}
          >
            {command}
          </Button>
        ))
      }
    </div>
  </Dialog>

  <Button id="astro-demo-dialog-header-input-trigger">Command Palette</Button>
</div>

<script>
  document
    .getElementById("astro-demo-dialog-header-input-trigger")
    ?.addEventListener("click", () => {
      window.__PindobaDialogManager?.open("astro-demo-dialog-header-input");
    });
</script>

Drawer

The drawer prop positions the dialog along an edge of the screen. Drawers animate in from their respective side and default to removing the border radius on the attached edge.

Right Drawer

right
This drawer slides in from the right edge of the screen.

Left Drawer

left
This drawer slides in from the left edge of the screen.

Top Drawer

top
This drawer slides in from the top edge of the screen.

Bottom Drawer

bottom
This drawer slides in from the bottom edge of the screen.
---
import Dialog from "../dialog.astro";
import Button from "@pindoba/astro-button";
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import { PanelBottom, PanelLeft, PanelRight, PanelTop } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "sm", direction: "row", flexWrap: "wrap" })}>
  <Dialog id="astro-demo-drawer-right" title="Right Drawer" drawer="right">
    <Stamp
      slot="heading-leading"
      shape="square"
      size="sm"
      feedback="primary"
      emphasis="secondary"
    >
      <PanelRight />
    </Stamp>
    <Badge slot="heading-trailing" size="sm">right</Badge>
    This drawer slides in from the right edge of the screen.
  </Dialog>

  <Dialog id="astro-demo-drawer-left" title="Left Drawer" drawer="left">
    <Stamp
      slot="heading-leading"
      shape="square"
      size="sm"
      feedback="primary"
      emphasis="secondary"
    >
      <PanelLeft />
    </Stamp>
    <Badge slot="heading-trailing" size="sm">left</Badge>
    This drawer slides in from the left edge of the screen.
  </Dialog>

  <Dialog id="astro-demo-drawer-top" title="Top Drawer" drawer="top">
    <Stamp
      slot="heading-leading"
      shape="square"
      size="sm"
      feedback="primary"
      emphasis="secondary"
    >
      <PanelTop />
    </Stamp>
    <Badge slot="heading-trailing" size="sm">top</Badge>
    This drawer slides in from the top edge of the screen.
  </Dialog>

  <Dialog id="astro-demo-drawer-bottom" title="Bottom Drawer" drawer="bottom">
    <Stamp
      slot="heading-leading"
      shape="square"
      size="sm"
      feedback="primary"
      emphasis="secondary"
    >
      <PanelBottom />
    </Stamp>
    <Badge slot="heading-trailing" size="sm">bottom</Badge>
    This drawer slides in from the bottom edge of the screen.
  </Dialog>

  <Button id="astro-demo-drawer-right-trigger" emphasis="secondary"
    >Right</Button
  >
  <Button id="astro-demo-drawer-left-trigger" emphasis="secondary">Left</Button>
  <Button id="astro-demo-drawer-top-trigger" emphasis="secondary">Top</Button>
  <Button id="astro-demo-drawer-bottom-trigger" emphasis="secondary"
    >Bottom</Button
  >
</div>

<script>
  const triggers: [string, string][] = [
    ["astro-demo-drawer-right-trigger", "astro-demo-drawer-right"],
    ["astro-demo-drawer-left-trigger", "astro-demo-drawer-left"],
    ["astro-demo-drawer-top-trigger", "astro-demo-drawer-top"],
    ["astro-demo-drawer-bottom-trigger", "astro-demo-drawer-bottom"],
  ];

  triggers.forEach(([triggerId, dialogId]) => {
    document.getElementById(triggerId)?.addEventListener("click", () => {
      window.__PindobaDialogManager?.open(dialogId);
    });
  });
</script>

Nested Drawers

Multiple dialogs can be stacked. Lower dialogs scale back and dim when a new one opens on top, creating a cascade effect. Each dialog is independently closable.

Open multiple drawers to see the cascade effect. Each drawer slides inward and dims when another opens on top.

Settings

1

This is the first level drawer. Click below to open another drawer on top.

Item 1: Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Item 2: Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Item 3: Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Item 4: Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Item 5: Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Item 6: Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Item 7: Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Item 8: Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Edit Profile

2

This is the second level. Notice how the first drawer moved back and dimmed.

Profile field 1: Some value here.

Profile field 2: Some value here.

Profile field 3: Some value here.

Profile field 4: Some value here.

Profile field 5: Some value here.

Profile field 6: Some value here.

Confirmation

3

This is the third level. All previous drawers are stacked behind.

---
import Dialog from "../dialog.astro";
import Button from "@pindoba/astro-button";
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import { CircleCheckBig, Settings, User } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";
---

<div class={stack({ gap: "md", direction: "column", align: "start" })}>
  <p class={css({ color: "neutral.text.subtle", fontSize: "sm" })}>
    Open multiple drawers to see the cascade effect. Each drawer slides inward
    and dims when another opens on top.
  </p>

  <Dialog id="astro-demo-nested-drawer-1" title="Settings" drawer="right">
    <Stamp slot="heading-leading" shape="circle" size="sm" emphasis="secondary">
      <Settings />
    </Stamp>
    <Badge slot="heading-trailing" size="sm">1</Badge>
    <div class={stack({ gap: "md" })}>
      <p>
        This is the first level drawer. Click below to open another drawer on
        top.
      </p>
      <Button id="astro-demo-nested-drawer-2-trigger" emphasis="secondary">
        Open Second Drawer
      </Button>
      {
        Array.from({ length: 8 }, (_, i) => (
          <p class={css({ color: "neutral.text.subtle" })}>
            Item {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing
            elit.
          </p>
        ))
      }
    </div>
  </Dialog>

  <Dialog id="astro-demo-nested-drawer-2" title="Edit Profile" drawer="right">
    <Stamp
      slot="heading-leading"
      shape="circle"
      size="sm"
      feedback="primary"
      emphasis="secondary"
    >
      <User />
    </Stamp>
    <Badge slot="heading-trailing" size="sm" feedback="primary">2</Badge>
    <div class={stack({ gap: "md" })}>
      <p>
        This is the second level. Notice how the first drawer moved back and
        dimmed.
      </p>
      <div class={stack({ gap: "sm" })}>
        <Button id="astro-demo-nested-drawer-3-trigger" emphasis="secondary">
          Open Third Drawer
        </Button>
        <Button id="astro-demo-nested-drawer-2-close" emphasis="ghost">
          Close This Drawer
        </Button>
      </div>
      {
        Array.from({ length: 6 }, (_, i) => (
          <p class={css({ color: "neutral.text.subtle" })}>
            Profile field {i + 1}: Some value here.
          </p>
        ))
      }
    </div>
  </Dialog>

  <Dialog id="astro-demo-nested-drawer-3" title="Confirmation" drawer="right">
    <Stamp
      slot="heading-leading"
      shape="circle"
      size="sm"
      feedback="success"
      emphasis="secondary"
    >
      <CircleCheckBig />
    </Stamp>
    <Badge slot="heading-trailing" size="sm" feedback="success">3</Badge>
    <div class={stack({ gap: "md" })}>
      <p>This is the third level. All previous drawers are stacked behind.</p>
      <div class={stack({ gap: "sm" })}>
        <Button id="astro-demo-nested-drawer-3-confirm" emphasis="primary"
          >Confirm</Button
        >
        <Button id="astro-demo-nested-drawer-3-cancel" emphasis="ghost"
          >Cancel</Button
        >
      </div>
    </div>
  </Dialog>

  <Button id="astro-demo-nested-drawer-1-trigger">Open First Drawer</Button>
</div>

<script>
  const triggers: [string, string, "open" | "close"][] = [
    [
      "astro-demo-nested-drawer-1-trigger",
      "astro-demo-nested-drawer-1",
      "open",
    ],
    [
      "astro-demo-nested-drawer-2-trigger",
      "astro-demo-nested-drawer-2",
      "open",
    ],
    [
      "astro-demo-nested-drawer-3-trigger",
      "astro-demo-nested-drawer-3",
      "open",
    ],
    ["astro-demo-nested-drawer-2-close", "astro-demo-nested-drawer-2", "close"],
    [
      "astro-demo-nested-drawer-3-confirm",
      "astro-demo-nested-drawer-3",
      "close",
    ],
    [
      "astro-demo-nested-drawer-3-cancel",
      "astro-demo-nested-drawer-3",
      "close",
    ],
  ];

  triggers.forEach(([triggerId, dialogId, action]) => {
    document.getElementById(triggerId)?.addEventListener("click", () => {
      window.__PindobaDialogManager?.[action](dialogId);
    });
  });
</script>

Custom Styling

The passThrough prop provides escape hatches for advanced customization: style applies Panda CSS SystemStyleObject to any slot, and props forwards arbitrary HTML attributes.

Custom Styled Dialog

This dialog uses passThrough for dialog-level slots andbannerPassThrough to reach Banner internals: a narrower max-width, a colored header border, and a bolder heading.
---
import Dialog from "../dialog.astro";
import Button from "@pindoba/astro-button";
import Stamp from "@pindoba/astro-stamp";
import { Sparkles } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "md", direction: "column", align: "start" })}>
  <Dialog
    id="astro-demo-dialog-custom"
    title="Custom Styled Dialog"
    passThrough={{
      wrapper: { style: { maxWidth: "360px" } },
      header: {
        style: {
          borderBottom: "1px solid",
          borderColor: "primary.border",
        },
      },
      content: { style: { gap: "md" } },
    }}
    bannerPassThrough={{
      heading: { style: { fontWeight: "bold", fontSize: "lg" } },
    }}
  >
    <Stamp
      slot="heading-leading"
      shape="square"
      size="sm"
      translucent
      border="bold"
    >
      <Sparkles />
    </Stamp>
    This dialog uses <code>passThrough</code> for dialog-level slots and
    <code>bannerPassThrough</code> to reach Banner internals: a narrower max-width,
    a colored header border, and a bolder heading.
  </Dialog>

  <Button id="astro-demo-dialog-custom-trigger">Open Custom Dialog</Button>
</div>

<script>
  document
    .getElementById("astro-demo-dialog-custom-trigger")
    ?.addEventListener("click", () => {
      window.__PindobaDialogManager?.open("astro-demo-dialog-custom");
    });
</script>
props · 38 shown · 38 total
background
"surface.peak""surface.hill""surface.base""surface.valley""surface.ground""transparent"
default "surface"

Background style of the dialog panel. surface: default surface; sunken: recessed; elevated: raised with shadow; transparent: no background.

bannerPassThrough svelte
BannerProps["passThrough"]

passThrough forwarded to the internal Banner.

border
"none""bold""default""muted""accent"
default "default"

Box-shadow border style of the dialog panel. none: no border; default: standard border; bold: stronger emphasis; muted: subtle separation.

closeButtonProps
Partial<DialogCloseButtonProps>

Props to customize the close button (merged with defaults).

customMouseDown
(e: MouseEvent) => void

Custom mousedown handler. When provided, replaces the default backdrop-click-to-close behavior. Used by Popover to also check the trigger element.

dialogElement svelte
HTMLDialogElementnull

No description yet.

drawer
"bottom""left""right""top"

Positions the dialog along an edge of the screen. Automatically removes the border radius on the attached edge unless overridden.

excludeFromStack
boolean
default false

When true, this dialog does not participate in the dialog stack. Useful for popovers and non-blocking overlays. Auto-detected for popovers.

feedback
"primary""neutral""success""warning""danger""inherit"

Semantic feedback color scheme applied to the dialog panel.

header slot svelte
Snippet

Replaces the header's Banner (title/subtitle) with custom content while keeping the header row's styling and the close button. Renders the header even when no `title`/`subtitle` is set. Used to embed an input (e.g. a command palette's search field) in the header.

headingLeading slot svelte
Snippet

Forwarded to Banner's heading `leading` slot (inline, left of the title).

headingTrailing slot svelte
Snippet

Forwarded to Banner's heading `trailing` slot (inline, right of the title).

isModal
boolean
default true

When true, opens as a modal dialog using showModal() — blocks interaction with the rest of the page and enables the backdrop.

leading slot svelte
Snippet

Forwarded to Banner's `leading` slot (far-left of the header row, outside heading/subheading).

lockScroll
boolean
default true

When true, prevents body scroll while the dialog is open. Uses a counter to handle stacked dialogs correctly.

onChange
(open: boolean) => void

Callback called when the dialog open state changes.

onClose
() => void

Callback called when the dialog closes.

onHeadlessReady svelte
(dialog: HeadlessDialog) => void

No description yet.

onOpen
() => void

Callback called when the dialog opens.

open required
boolean
default false

Controls the open/closed state of the dialog. Bindable in Svelte.

outsideContent slot svelte
Snippet

No description yet.

padding
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""7xl""8xl""4xs""3xs""2xs""9xl""10xl""11xl"
default "none"

Internal padding of the dialog panel.

passThrough
DialogPassThrough< RootElementAttributes, WrapperElementAttributes, HeaderElementAttributes, ContentElementAttributes, CloseButtonElementAttributes >

Per-slot style and attribute override bag for dialog-owned slots. `style` applies Panda CSS to a slot; `props` forwards arbitrary HTML attributes.

radius
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"
default "md"

Border radius of the dialog panel. Automatically set to none on the attached edge for drawer dialogs.

radiusBottom
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Border radius for bottom-left and bottom-right corners.

radiusLeft
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Border radius for top-left and bottom-left corners.

radiusRight
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Border radius for top-right and bottom-right corners.

radiusTop
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Border radius for top-left and top-right corners.

shadow
"sm""md""lg""xl""none""xs"
default "xl"

Elevation shadow of the dialog panel. Dialogs float above the page, so they carry a shadow by default; pass "none" to flatten it.

showCloseButton
boolean
default true

Whether to show the close button in the header.

subheadingLeading slot svelte
Snippet

Forwarded to Banner's subheading `leading` slot.

subheadingTrailing slot svelte
Snippet

Forwarded to Banner's subheading `trailing` slot.

subtitle
nullstring

Secondary text displayed below the title. Renders the subheading row, which also accepts subheading-leading and subheading-trailing slots.

title
nullstring

Text displayed in the dialog header. When provided, the header is rendered with the title, close button, and any heading or subheading slots.

trailing slot svelte
Snippet

Forwarded to Banner's `trailing` slot (far-right of the header row, before the close button).

translucent
boolean
default false

Apply a frosted glass effect with backdrop blur to the dialog panel.

trigger slot svelte
Snippet<[TriggerSnippetProps]>

No description yet.

triggerElement svelte
HTMLElementnull

No description yet.

Plus all standard <dialog> HTML attributes.

Type

  • Components
  • Blocks