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.
A centered modal dialog with title, content, and a close button.
---
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.
System will be unavailable on March 15, 2026
Browse and manage your project files
Manage your preferences and security
---
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>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.
---
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>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.
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.
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.
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>The passThrough prop provides escape hatches for advanced customization: style applies Panda CSS SystemStyleObject to any slot, and props forwards arbitrary HTML attributes.
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>| Prop | |
|---|---|
open | open Controls the open/closed state of the dialog. Bindable in Svelte. Type Default Required |
isModal | isModal When true, opens as a modal dialog using showModal() — blocks interaction with the rest of the page and enables the backdrop. Type Default Required |
drawer | drawer Positions the dialog along an edge of the screen. Automatically removes the border radius on the attached edge unless overridden. Type Default Required |
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 Default Required |
subtitle | subtitle Secondary text displayed below the title. Renders the subheading row, which also accepts subheading-leading and subheading-trailing slots. Type Default Required |
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 Default Required |
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 Default Required |
subheadingLeading | subheadingLeading Content placed to the left of the subtitle. Only rendered when subtitle is set. (Svelte: Snippet / Astro: subheading-leading slot) Type Default Required |
subheadingTrailing | subheadingTrailing Content placed to the right of the subtitle. Only rendered when subtitle is set. (Svelte: Snippet / Astro: subheading-trailing slot) Type Default Required |
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 Default Required |
showCloseButton | showCloseButton Whether to show the close button in the header. Type Default Required |
lockScroll | lockScroll When true, prevents body scroll while the dialog is open. Uses a counter to handle stacked dialogs correctly. Type Default Required |
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 Default Required |
onChange | onChange Callback called when the dialog open state changes. Type Default Required |
onOpen | onOpen Callback called when the dialog opens. Type Default Required |
onClose | onClose Callback called when the dialog closes. Type Default Required |
background | background Background style of the dialog panel. surface: Default surface. sunken: Recessed. elevated: Raised with shadow. transparent: No background. Type Default Required |
accent | accent Apply the full-color (500) accent background from the current feedback color with contrast text. Type Default Required |
translucent | translucent Apply a frosted glass effect with backdrop blur to the dialog panel. Type Default Required |
feedback | feedback Semantic feedback color scheme applied to the dialog panel. Type Default Required |
padding | padding Internal padding of the dialog panel. Type Default Required |
radius | radius Border radius of the dialog panel. Automatically set to none on the attached edge for drawer dialogs. Type Default Required |
radiusTop | radiusTop Border radius for top-left and top-right corners. Type Default Required |
radiusBottom | radiusBottom Border radius for bottom-left and bottom-right corners. Type Default Required |
radiusLeft | radiusLeft Border radius for top-left and bottom-left corners. Type Default Required |
radiusRight | radiusRight Border radius for top-right and bottom-right corners. Type Default Required |
border | border Box-shadow border style of the dialog panel. none: No border. default: Standard border. bold: Stronger emphasis. muted: Subtle separation. Type Default Required |
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 Default Required |
dialogElement | dialogElement Bindable reference to the underlying HTMLDialogElement. In Svelte, use bind:dialogElement to access the DOM node. Type Default Required |
outsideContent | outsideContent Snippet rendered outside the Panel wrapper but inside the dialog root. Useful for custom backdrops or overlapping elements. (Svelte only) Type Default Required |
children | children Dialog content rendered inside the content slot. Type Default Required |
...rest | ...rest Standard HTML dialog attributes forwarded to the root dialog element. Type Default Required |