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 { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "md", direction: "column", align: "start" })}>
  <Dialog id="astro-demo-dialog-default" title="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!
  </Dialog>

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

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

The subtitle prop adds a secondary line of text below the title. Both the heading and the subtitle support leading and trailing slots for icons, badges, or other inline elements — use heading slots for content that relates to the title, and subheading slots for content that relates to the subtitle.

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.
---
import Dialog from "../dialog.astro";
import Button from "@pindoba/astro-button";
import Badge from "@pindoba/astro-badge";
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>

  <!-- Heading leading icon + trailing badge -->
  <Dialog
    id="astro-demo-dialog-header-files"
    title="File Manager"
    subtitle="Browse and manage your project files"
  >
    <svg
      slot="heading-leading"
      xmlns="http://www.w3.org/2000/svg"
      width="16"
      height="16"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
    >
      <path
        d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
      ></path>
    </svg>
    <Badge slot="heading-trailing" size="xs">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">Heading Slots</Button>

  <!-- Heading icon + subheading leading icon + subheading trailing badge -->
  <Dialog
    id="astro-demo-dialog-header-account"
    title="Account Settings"
    subtitle="Manage your preferences and security"
  >
    <svg
      slot="heading-leading"
      xmlns="http://www.w3.org/2000/svg"
      width="16"
      height="16"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
    >
      <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path>
      <circle cx="12" cy="7" r="4"></circle>
    </svg>
    <svg
      slot="subheading-leading"
      xmlns="http://www.w3.org/2000/svg"
      width="14"
      height="14"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
    >
      <path
        d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"
      ></path>
      <path d="m9 12 2 2 4-4"></path>
    </svg>
    <Badge slot="subheading-trailing" size="xs" 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
  >
</div>

<script>
  document
    .getElementById("astro-demo-dialog-header-notice-trigger")
    ?.addEventListener("click", () => {
      window.__PindobaDialogManager?.showModal(
        "astro-demo-dialog-header-notice",
      );
    });
  document
    .getElementById("astro-demo-dialog-header-files-trigger")
    ?.addEventListener("click", () => {
      window.__PindobaDialogManager?.showModal(
        "astro-demo-dialog-header-files",
      );
    });
  document
    .getElementById("astro-demo-dialog-header-account-trigger")
    ?.addEventListener("click", () => {
      window.__PindobaDialogManager?.showModal(
        "astro-demo-dialog-header-account",
      );
    });
</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
This drawer slides in from the right edge of the screen.
Left Drawer
This drawer slides in from the left edge of the screen.
Top Drawer
This drawer slides in from the top edge of the screen.
Bottom Drawer
This drawer slides in from the bottom edge of the screen.
---
import Dialog from "../dialog.astro";
import Button from "@pindoba/astro-button";
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">
    This drawer slides in from the right edge of the screen.
  </Dialog>

  <Dialog id="astro-demo-drawer-left" title="Left Drawer" drawer="left">
    This drawer slides in from the left edge of the screen.
  </Dialog>

  <Dialog id="astro-demo-drawer-top" title="Top Drawer" drawer="top">
    This drawer slides in from the top edge of the screen.
  </Dialog>

  <Dialog id="astro-demo-drawer-bottom" title="Bottom Drawer" drawer="bottom">
    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?.showModal(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

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

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

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

---
import Dialog from "../dialog.astro";
import Button from "@pindoba/astro-button";
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">
    <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">
    <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">
    <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, "showModal" | "close"][] = [
    [
      "astro-demo-nested-drawer-1-trigger",
      "astro-demo-nested-drawer-1",
      "showModal",
    ],
    [
      "astro-demo-nested-drawer-2-trigger",
      "astro-demo-nested-drawer-2",
      "showModal",
    ],
    [
      "astro-demo-nested-drawer-3-trigger",
      "astro-demo-nested-drawer-3",
      "showModal",
    ],
    ["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 to customize slot styles: a narrower max-width, a border under the header, and a bolder title.
---
import Dialog from "../dialog.astro";
import Button from "@pindoba/astro-button";
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: "neutral.border.muted",
        },
      },
      title: { style: { fontWeight: "bold", fontSize: "lg" } },
      content: { style: { gap: "md" } },
    }}
  >
    This dialog uses <code>passThrough</code> to customize slot styles: a narrower
    max-width, a border under the header, and a bolder title.
  </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?.showModal("astro-demo-dialog-custom");
    });
</script>

Props

Prop
open
open

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

Type boolean
Default false
Required Yes
isModal
isModal

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

Type boolean
Default true
Required No
drawer
drawer

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

Type "left" | "right" | "top" | "bottom"
Default undefined
Required No
title
title

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

Type string
Default undefined
Required No
subtitle
subtitle

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

Type string
Default undefined
Required No
headingLeading
headingLeading

Content placed to the left of the title. Useful for icons that identify the dialog type. (Svelte: Snippet / Astro: heading-leading slot)

Type Snippet | slot
Default undefined
Required No
headingTrailing
headingTrailing

Content placed to the right of the title within the heading row. Useful for status badges or secondary actions. (Svelte: Snippet / Astro: heading-trailing slot)

Type Snippet | slot
Default undefined
Required No
subheadingLeading
subheadingLeading

Content placed to the left of the subtitle. Only rendered when subtitle is set. (Svelte: Snippet / Astro: subheading-leading slot)

Type Snippet | slot
Default undefined
Required No
subheadingTrailing
subheadingTrailing

Content placed to the right of the subtitle. Only rendered when subtitle is set. (Svelte: Snippet / Astro: subheading-trailing slot)

Type Snippet | slot
Default undefined
Required No
headerActions
headerActions

Additional action buttons or controls rendered in the header to the left of the close button. (Svelte: Snippet / Astro: header-actions slot)

Type Snippet | slot
Default undefined
Required No
showCloseButton
showCloseButton

Whether to show the close button in the header.

Type boolean
Default true
Required No
lockScroll
lockScroll

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

Type boolean
Default true
Required No
excludeFromStack
excludeFromStack

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

Type boolean
Default false
Required No
onChange
onChange

Callback called when the dialog open state changes.

Type (open: boolean) => void
Default undefined
Required No
onOpen
onOpen

Callback called when the dialog opens.

Type () => void
Default undefined
Required No
onClose
onClose

Callback called when the dialog closes.

Type () => void
Default undefined
Required No
background
background

Background style of the dialog panel. surface: Default surface. sunken: Recessed. elevated: Raised with shadow. transparent: No background.

Type "surface" | "sunken" | "elevated" | "transparent"
Default "surface"
Required No
accent
accent

Apply the full-color (500) accent background from the current feedback color with contrast text.

Type boolean
Default false
Required No
translucent
translucent

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

Type boolean
Default false
Required No
feedback
feedback

Semantic feedback color scheme applied to the dialog panel.

Type "default" | "success" | "warning" | "danger"
Default undefined
Required No
padding
padding

Internal padding of the dialog panel.

Type "none" | "5xs" | "4xs" | "3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "7xl" | "8xl" | "9xl" | "10xl" | "11xl"
Default "none"
Required No
radius
radius

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

Type "none" | "5xs" | "4xs" | "3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "7xl" | "8xl" | "9xl" | "10xl" | "11xl" | "full"
Default "md"
Required No
radiusTop
radiusTop

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

Type SpacingScale
Default undefined
Required No
radiusBottom
radiusBottom

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

Type SpacingScale
Default undefined
Required No
radiusLeft
radiusLeft

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

Type SpacingScale
Default undefined
Required No
radiusRight
radiusRight

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

Type SpacingScale
Default undefined
Required No
border
border

Box-shadow border style of the dialog panel. none: No border. default: Standard border. bold: Stronger emphasis. muted: Subtle separation.

Type "none" | "default" | "bold" | "muted"
Default "default"
Required No
passThrough
passThrough

Custom styling and props for dialog slots. Each slot accepts style (SystemStyleObject) and props (HTML attributes). Available slots: root, wrapper, header, headingGroup, headingContainer, title, headingLeading, headingTrailing, subheadingContainer, subheading, subheadingLeading, subheadingTrailing, headerActions, content, closeButton.

Type { root?: { style?: SystemStyleObject; props?: HTMLAttributes<HTMLDialogElement> }; wrapper?: { style?: SystemStyleObject; props?: HTMLAttributes<HTMLDivElement> }; header?: { style?: SystemStyleObject; props?: HTMLAttributes<HTMLDivElement> }; title?: { style?: SystemStyleObject; props?: HTMLAttributes<HTMLDivElement> }; subheading?: { style?: SystemStyleObject; props?: HTMLAttributes<HTMLParagraphElement> }; content?: { style?: SystemStyleObject; props?: HTMLAttributes<HTMLDivElement> }; closeButton?: { style?: SystemStyleObject; props?: Omit<ButtonProps, 'onclick'> } }
Default undefined
Required No
dialogElement
dialogElement

Bindable reference to the underlying HTMLDialogElement. In Svelte, use bind:dialogElement to access the DOM node.

Type HTMLDialogElement
Default undefined
Required No
outsideContent
outsideContent

Snippet rendered outside the Panel wrapper but inside the dialog root. Useful for custom backdrops or overlapping elements. (Svelte only)

Type Snippet
Default undefined
Required No
children
children

Dialog content rendered inside the content slot.

Type Snippet | slot
Default undefined
Required No
...rest
...rest

Standard HTML dialog attributes forwarded to the root dialog element.

Type HTMLAttributes<HTMLDialogElement>
Default -
Required No