component
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 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.
---
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>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>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 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>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 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>The passThrough prop provides escape hatches for advanced customization: style applies Panda CSS SystemStyleObject to any slot, and props forwards arbitrary HTML attributes.
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>"surface"Background style of the dialog panel. surface: default surface; sunken: recessed; elevated: raised with shadow; transparent: no background.
passThrough forwarded to the internal Banner.
"default"Box-shadow border style of the dialog panel. none: no border; default: standard border; bold: stronger emphasis; muted: subtle separation.
Props to customize the close button (merged with defaults).
Custom mousedown handler. When provided, replaces the default backdrop-click-to-close behavior. Used by Popover to also check the trigger element.
No description yet.
Positions the dialog along an edge of the screen. Automatically removes the border radius on the attached edge unless overridden.
falseWhen true, this dialog does not participate in the dialog stack. Useful for popovers and non-blocking overlays. Auto-detected for popovers.
Semantic feedback color scheme applied to the dialog panel.
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.
Forwarded to Banner's heading `leading` slot (inline, left of the title).
Forwarded to Banner's heading `trailing` slot (inline, right of the title).
trueWhen true, opens as a modal dialog using showModal() — blocks interaction with the rest of the page and enables the backdrop.
Forwarded to Banner's `leading` slot (far-left of the header row, outside heading/subheading).
trueWhen true, prevents body scroll while the dialog is open. Uses a counter to handle stacked dialogs correctly.
Callback called when the dialog open state changes.
Callback called when the dialog closes.
No description yet.
Callback called when the dialog opens.
falseControls the open/closed state of the dialog. Bindable in Svelte.
No description yet.
"none"Internal padding of the dialog panel.
Per-slot style and attribute override bag for dialog-owned slots. `style` applies Panda CSS to a slot; `props` forwards arbitrary HTML attributes.
"md"Border radius of the dialog panel. Automatically set to none on the attached edge for drawer dialogs.
Border radius for bottom-left and bottom-right corners.
Border radius for top-left and bottom-left corners.
Border radius for top-right and bottom-right corners.
Border radius for top-left and top-right corners.
"xl"Elevation shadow of the dialog panel. Dialogs float above the page, so they carry a shadow by default; pass "none" to flatten it.
trueWhether to show the close button in the header.
Forwarded to Banner's subheading `leading` slot.
Forwarded to Banner's subheading `trailing` slot.
Secondary text displayed below the title. Renders the subheading row, which also accepts subheading-leading and subheading-trailing slots.
Text displayed in the dialog header. When provided, the header is rendered with the title, close button, and any heading or subheading slots.
Forwarded to Banner's `trailing` slot (far-right of the header row, before the close button).
falseApply a frosted glass effect with backdrop blur to the dialog panel.
No description yet.
No description yet.
Plus all standard <dialog> HTML
attributes.