Dialog
A modal layer — a centred panel over a scrim. Shares the `layer` family with alert-dialog and drawer; the only things this component declares for itself are `Root`, `Trigger`, `Portal` and the centring transform.
npx shadcn@latest add @fragiola/dialogShow code
"use client";
import type { ReactNode } from "react";
import { Clickable } from "#/atoms/clickable";
import { Input } from "#/atoms/fields";
import { Dialog } from "#/ui/dialog";
import { Field } from "#/ui/field";
// The floor is palette-surface. The dialog content travels through a portal
// but inherits the palette from the owning subtree — the header, body,
// footer and fields all read roles from the floor.
const TERMS_SECTIONS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
export default function DialogDemo() {
return (
<div className="palette-surface flex flex-col gap-6 rounded-lg border border-palette-line bg-palette-base p-6">
<Row label="form dialog">
<Dialog.Root>
<Dialog.Trigger
render={<Clickable.Button variant="outline" />}
>
Edit profile
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Backdrop />
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Edit profile</Dialog.Title>
<Dialog.Description>
Make changes to your profile here. Click
save when you are done.
</Dialog.Description>
</Dialog.Header>
<Dialog.Body>
<Field.Root>
<Field.Label>Name</Field.Label>
<Field.Row>
<Field.Body>
<Input defaultValue="Fragiola" />
</Field.Body>
</Field.Row>
</Field.Root>
<Field.Root>
<Field.Label>Username</Field.Label>
<Field.Row>
<Field.Body>
<Input defaultValue="@fragiola" />
</Field.Body>
</Field.Row>
</Field.Root>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close
render={
<Clickable.Button variant="outline" />
}
>
Cancel
</Dialog.Close>
<Clickable.Button variant="solid">
Save
</Clickable.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</Row>
<Row label="scrolling body">
<Dialog.Root>
<Dialog.Trigger
render={<Clickable.Button variant="outline" />}
>
Terms
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Backdrop />
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Terms of service</Dialog.Title>
<Dialog.Description>
Please read the full terms before
continuing.
</Dialog.Description>
</Dialog.Header>
<Dialog.Body className="max-h-60">
{TERMS_SECTIONS.map((n) => (
<p
key={n}
className="text-sm text-palette-accent/85"
>
Section {n}: Fragiola is a copy-paste
component library built on Base UI
primitives and Tailwind v4. The palette
contract is six roles; the number of
palettes is free.
</p>
))}
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close
render={
<Clickable.Button variant="outline" />
}
>
Close
</Dialog.Close>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</Row>
</div>
);
}
function Row({ label, children }: { label: string; children: ReactNode }) {
return (
<div className="flex flex-col gap-2">
<span className="text-xs font-mono text-palette-accent/85">
{label}
</span>
<div className="flex flex-wrap items-center gap-4">{children}</div>
</div>
);
}
Style families
dialog, alert-dialog and drawer render from the same layer source — backdrop, panel, header, body, footer, title, description. The parts factory (createLayerParts) takes a Base UI namespace and returns the styled wrappers. Installing alert-dialog after dialog writes exactly one file.
Title and description point at Text
Dialog.Title is Text.Heading as="h2" wearing the primitive's a11y wiring, stitched with render. Dialog.Description is Text.Paragraph. Neither declares its own typography — this is the rule that keeps seventeen title classes from coming back.
Parts
| Part | Source |
|---|---|
Root / Trigger / Portal | Base UI Dialog |
Backdrop | layer.backdrop() (bg-scrim) |
Content | layer.panel() + centring transform |
Header / Body / Footer | layer.header/body/footer() |
Title | Text.Heading as="h2" via render |
Description | Text.Paragraph via render |
Close | Clickable.Button via render |
Context Menu
A menu of actions triggered by a right-click. Shares the `popup` and `menu` style families with dropdown-menu — installing it after `dropdown-menu` writes exactly one file.
Alert Dialog
A modal layer that requires an explicit action to dismiss — no backdrop click, no close button. This file is the proof that `layer` is a family: it declares only what genuinely differs from `dialog`.