Dialog
Dialog Examples
Copy-ready examples with live previews.
Basic usage
Externally controlled dialog with text, close button, and actions footer.
Live preview
Basic dialog
import { useState } from "react";
import Dialog from "@/packages/ui/src/_components/dialog/Dialog";
import { Title } from "@/packages/ui/src/_components/text/title/Title";
import { Paragraph } from "@/packages/ui/src/_components/text/paragraph/Paragraph";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";
import Icon from "@/packages/ui/src/_components/icon/Icon";
function SafePress(props: { onPress: () => void; children: React.ReactNode }) {
return (
<span
style={{ display: "inline-flex" }}
onClickCapture={(e) => {
e.preventDefault();
e.stopPropagation();
props.onPress();
}}
>
{props.children}
</span>
);
}
export default function Example() {
const [open, setOpen] = useState(false);
return (
<>
{open ? null : (
<Button variant="primary" ripple outline onClick={() => setOpen(true)}>
Open dialog
</Button>
)}
<Dialog
open={open}
variant="basic"
size="md"
headerVariant="basic"
footerVariant="actions"
closeBehavior="button"
showCloseButton
closeButton={
<SafePress onPress={() => setOpen(false)}>
<Button variant="transparent" variantRadio="sm" ripple ariaLabel="Close dialog">
<Icon name="Close" variant="black" />
</Button>
</SafePress>
}
title={<Title size="h4">Basic dialog</Title>}
actionsContent={
<>
<SafePress onPress={() => setOpen(false)}>
<Button variant="secondary" ripple outline>Cancel</Button>
</SafePress>
<SafePress onPress={() => setOpen(false)}>
<Button variant="primary" ripple outline>Confirm</Button>
</SafePress>
</>
}
>
<Paragraph size="medium">Dialog is layout-only. Closing is controlled by your app.</Paragraph>
</Dialog>
</>
);
}Scroll + divider
Use divider and a long body to validate overflow behavior.
Live preview
Terms dialog
import { useMemo, useState } from "react";
import Dialog from "@/packages/ui/src/_components/dialog/Dialog";
import { Title } from "@/packages/ui/src/_components/text/title/Title";
import { Paragraph } from "@/packages/ui/src/_components/text/paragraph/Paragraph";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";
import Icon from "@/packages/ui/src/_components/icon/Icon";
function SafePress(props: { onPress: () => void; children: React.ReactNode }) {
return (
<span
style={{ display: "inline-flex" }}
onClickCapture={(e) => {
e.preventDefault();
e.stopPropagation();
props.onPress();
}}
>
{props.children}
</span>
);
}
export default function Example() {
const [open, setOpen] = useState(false);
const paragraphs = useMemo(
() => Array.from({ length: 18 }).map((_, i) => (
<Paragraph key={i} size="medium">Paragraph {i + 1}: filler text to validate scrolling.</Paragraph>
)),
[]
);
return (
<>
{open ? null : (
<Button variant="tertiary" ripple outline onClick={() => setOpen(true)}>
Open (scroll + divider)
</Button>
)}
<Dialog
open={open}
variant="basic"
size="md"
divider
headerVariant="basic"
footerVariant="actions"
closeBehavior="both"
showCloseButton
closeButton={
<SafePress onPress={() => setOpen(false)}>
<Button variant="transparent" variantRadio="sm" ripple>
<Icon name="Close" variant="black" />
</Button>
</SafePress>
}
title={<Title size="h4">Terms</Title>}
actionsContent={
<>
<SafePress onPress={() => setOpen(false)}>
<Button variant="secondary" ripple outline>Reject</Button>
</SafePress>
<SafePress onPress={() => setOpen(false)}>
<Button variant="primary" ripple outline>Accept</Button>
</SafePress>
</>
}
>
<div style={{ display: "grid", gap: 12 }}>{paragraphs}</div>
</Dialog>
</>
);
}Size and variant
Toggle size ("sm" | "md" | "lg") and variant ("basic" | "full").
Live preview
Size/variant toggles
import { useState } from "react";
import Dialog from "@/packages/ui/src/_components/dialog/Dialog";
import { Title } from "@/packages/ui/src/_components/text/title/Title";
import { Paragraph } from "@/packages/ui/src/_components/text/paragraph/Paragraph";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";
import Icon from "@/packages/ui/src/_components/icon/Icon";
function SafePress(props: { onPress: () => void; children: React.ReactNode }) {
return (
<span
style={{ display: "inline-flex" }}
onClickCapture={(e) => {
e.preventDefault();
e.stopPropagation();
props.onPress();
}}
>
{props.children}
</span>
);
}
export default function Example() {
const [open, setOpen] = useState(false);
const [size, setSize] = useState<"sm" | "md" | "lg">("sm");
const [variant, setVariant] = useState<"basic" | "full">("basic");
return (
<>
<div style={{ display: "flex", gap: 12, flexWrap: "wrap", alignItems: "center" }}>
{open ? null : (
<Button variant="success" ripple outline onClick={() => setOpen(true)}>Open</Button>
)}
<Button variant="secondary" ripple outline onClick={() => setSize((s) => (s === "sm" ? "md" : s === "md" ? "lg" : "sm"))}>
Size: {size}
</Button>
<Button variant="secondary" ripple outline onClick={() => setVariant((v) => (v === "basic" ? "full" : "basic"))}>
Variant: {variant}
</Button>
</div>
<Dialog
open={open}
variant={variant}
size={size}
headerVariant={variant === "full" ? "full" : "basic"}
footerVariant="actions"
closeBehavior="button"
showCloseButton
closeButton={
<SafePress onPress={() => setOpen(false)}>
<Button variant="transparent" variantRadio="sm" ripple>
<Icon name="Close" variant="black" />
</Button>
</SafePress>
}
title={<Title size="h4">Size + Variant</Title>}
actionsContent={
<>
<SafePress onPress={() => setOpen(false)}>
<Button variant="secondary" ripple outline>Cancel</Button>
</SafePress>
<SafePress onPress={() => setOpen(false)}>
<Button variant="primary" ripple outline>Confirm</Button>
</SafePress>
</>
}
>
<Paragraph size="medium">Validate layout presets quickly.</Paragraph>
</Dialog>
</>
);
}Header/Footer overrides
Provide full custom nodes using header and footer.
Live preview
Custom header + custom footer
import { useState } from "react";
import Dialog from "@/packages/ui/src/_components/dialog/Dialog";
import { Title } from "@/packages/ui/src/_components/text/title/Title";
import { Paragraph } from "@/packages/ui/src/_components/text/paragraph/Paragraph";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";
import Icon from "@/packages/ui/src/_components/icon/Icon";
function SafePress(props: { onPress: () => void; children: React.ReactNode }) {
return (
<span
style={{ display: "inline-flex" }}
onClickCapture={(e) => {
e.preventDefault();
e.stopPropagation();
props.onPress();
}}
>
{props.children}
</span>
);
}
export default function Example() {
const [open, setOpen] = useState(false);
return (
<>
{open ? null : (
<Button variant="info" ripple outline onClick={() => setOpen(true)}>
Open (override)
</Button>
)}
<Dialog
open={open}
variant="basic"
size="md"
closeBehavior="button"
header={
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
<div style={{ display: "grid", gap: 2 }}>
<Title size="h5">Custom header</Title>
<span style={{ opacity: 0.75 }}>Replaces default header.</span>
</div>
<SafePress onPress={() => setOpen(false)}>
<Button variant="transparent" variantRadio="sm" ripple>
<Icon name="Close" variant="black" />
</Button>
</SafePress>
</div>
}
footer={
<div style={{ display: "flex", justifyContent: "flex-end", gap: 10, flexWrap: "wrap" }}>
<SafePress onPress={() => setOpen(false)}>
<Button variant="secondary" ripple outline>Dismiss</Button>
</SafePress>
<SafePress onPress={() => setOpen(false)}>
<Button variant="primary" ripple outline>Save</Button>
</SafePress>
</div>
}
>
<Paragraph size="medium">Full control over header/footer layout.</Paragraph>
</Dialog>
</>
);
}Class hooks
Pass class names for targeting CSS and animations.
Live preview
Internal class hooks
import { useState } from "react";
import Dialog from "@/packages/ui/src/_components/dialog/Dialog";
import { Title } from "@/packages/ui/src/_components/text/title/Title";
import { Paragraph } from "@/packages/ui/src/_components/text/paragraph/Paragraph";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";
import Icon from "@/packages/ui/src/_components/icon/Icon";
function SafePress(props: { onPress: () => void; children: React.ReactNode }) {
return (
<span
style={{ display: "inline-flex" }}
onClickCapture={(e) => {
e.preventDefault();
e.stopPropagation();
props.onPress();
}}
>
{props.children}
</span>
);
}
export default function Example() {
const [open, setOpen] = useState(false);
return (
<>
{open ? null : (
<Button variant="secondary" ripple outline onClick={() => setOpen(true)}>
Open (class hooks)
</Button>
)}
<Dialog
open={open}
variant="basic"
size="md"
headerVariant="basic"
footerVariant="actions"
closeBehavior="button"
showCloseButton
closeButton={
<SafePress onPress={() => setOpen(false)}>
<Button variant="transparent" variantRadio="sm" ripple>
<Icon name="Close" variant="black" />
</Button>
</SafePress>
}
title={<Title size="h4">Styling hooks</Title>}
actionsContent={
<>
<SafePress onPress={() => setOpen(false)}>
<Button variant="secondary" ripple outline>Cancel</Button>
</SafePress>
<SafePress onPress={() => setOpen(false)}>
<Button variant="primary" ripple outline>Confirm</Button>
</SafePress>
</>
}
className="docs-dialog-demo"
scrimClassName="docs-dialog-demo__scrim"
surfaceClassName="docs-dialog-demo__surface"
bodyClassName="docs-dialog-demo__body"
footerClassName="docs-dialog-demo__footer"
>
<Paragraph size="medium">Use data-open / is-open for animations.</Paragraph>
</Dialog>
</>
);
}On this page
Dialog Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| open | boolean | Yes | — | Controls open state (adds "is-open" and data-open="true|false"). |
| children | React.ReactNode | No | — | Body content. |
| title | React.ReactNode | No | — | Title used by the default header composition (when header is not provided). |
| iconName | string | No | — | Optional icon name used by the default header composition. |
| iconVariant | string | No | — | Reserved (currently not used by Dialog.tsx). |
| header | React.ReactNode | No | — | Full header override (replaces default header composition). |
| showCloseButton | boolean | No | false | When true, allows rendering `closeButton` inside the default header. |
| closeButton | React.ReactNode | No | — | Close button node for the header (rendered only when showCloseButton is true). |
| closeAriaLabel | string | No | — | Reserved (currently not used by Dialog.tsx). |
| footer | React.ReactNode | No | — | Full footer override (has priority over actionsContent). |
| actionsContent | React.ReactNode | No | — | Quick footer slot used when `footer` is not provided. |
| variant | "basic" | "full" | No | "basic" | Visual variant (maps to modifier classes). |
| size | "sm" | "md" | "lg" | No | "md" | Size preset (maps to modifier classes). |
| divider | boolean | No | false | Adds a divider modifier class between header/body and/or body/footer. |
| headerVariant | "none" | "basic" | "full" | No | — | Header layout preset (maps to modifier classes). |
| footerVariant | "none" | "actions" | No | — | Footer layout preset (maps to modifier classes). |
| closeBehavior | "none" | "overlay" | "button" | "both" | No | — | Close layout preset (maps to modifier classes). |
| className | string | No | — | Extra classes for the root element. |
| customStyles | React.CSSProperties | No | — | Inline styles for the root element. |
| scrimClassName | string | No | — | Extra classes for the scrim element (.cssnt-dialog__scrim). |
| surfaceClassName | string | No | — | Extra classes for the surface element (.cssnt-dialog__surface). |
| bodyClassName | string | No | — | Extra classes for the body element (.cssnt-dialog__body). |
| footerClassName | string | No | — | Extra classes for the footer element (.cssnt-dialog__footer). |
Components