Popover
A floating surface positioned against a trigger. Composes `popup.content` — the same floating box dropdown-menu uses — and adds Title, Description and Close through `render`.
npx shadcn@latest add @fragiola/popoverShow code
"use client";
import type { ReactNode } from "react";
import { Clickable } from "#/atoms/clickable";
import { Input } from "#/atoms/fields";
import { Field } from "#/ui/field";
import { Popover } from "#/ui/popover";
// The floor is palette-surface. The popover content travels through a portal
// but inherits the palette from the owning subtree — the title, description,
// fields and buttons all read roles from the floor. Shows: a popover with a
// form, and one without a close button.
export default function PopoverDemo() {
return (
<div className="palette-surface flex flex-col gap-6 rounded-lg border border-palette-line bg-palette-base p-6">
<Row label="with form">
<Popover.Root>
<Popover.Trigger
render={<Clickable.Button variant="outline" />}
>
Open popover
</Popover.Trigger>
<Popover.Content align="start" sideOffset={4}>
<div className="flex flex-col gap-3 p-4">
<div className="flex flex-col gap-1">
<Popover.Title>Dimensions</Popover.Title>
<Popover.Description>
Set the dimensions for the canvas.
</Popover.Description>
</div>
<Field.Root>
<Field.Label>Width</Field.Label>
<Field.Row>
<Field.Body>
<Input defaultValue="100%" />
</Field.Body>
</Field.Row>
</Field.Root>
<Field.Root>
<Field.Label>Height</Field.Label>
<Field.Row>
<Field.Body>
<Input defaultValue="auto" />
</Field.Body>
</Field.Row>
</Field.Root>
<div className="flex justify-end gap-2">
<Popover.Close
render={
<Clickable.Button
variant="outline"
size="sm"
/>
}
>
Cancel
</Popover.Close>
<Clickable.Button variant="solid" size="sm">
Apply
</Clickable.Button>
</div>
</div>
</Popover.Content>
</Popover.Root>
</Row>
<Row label="no close button">
<Popover.Root>
<Popover.Trigger
render={<Clickable.Button variant="outline" />}
>
No close
</Popover.Trigger>
<Popover.Content showClose={false} align="start">
<div className="flex flex-col gap-2 p-4">
<Popover.Title>Quick info</Popover.Title>
<Popover.Description>
A popover without a close button — dismissed by
clicking outside or pressing Escape.
</Popover.Description>
</div>
</Popover.Content>
</Popover.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>
);
}
popup.content, not layer.panel
A popover is positioned by a floating-ui positioner and sized by --available-height; a dialog panel is not. The overlap (raised surface, border, radius, shadow) is checked, not assumed — and it is small enough that unifying them would couple a positioned surface to a centring transform it does not use. They stay separate: layer.panel for modal surfaces, popup.content for positioned surfaces.
Title and description point at Text
Popover.Title is Text.Heading as="h2" wearing the primitive's a11y wiring, stitched with render. Popover.Description is Text.Paragraph. The close affordance is Clickable.Button variant="icon", positioned with logical properties.
Parts
| Part | Source |
|---|---|
Root / Trigger | Base UI Popover |
Content | popup.content() + Portal + Positioner |
Title | Text.Heading as="h2" via render |
Description | Text.Paragraph via render |
Close | Clickable.Button via render |
Drawer
An edge-anchored layer with real swipe and snap-point support. Built on Base UI's `Drawer` primitive, which is why this component exists and why `sheet` does not.
Tooltip
A transient text label that appears on hover or focus. Composes the `popup.tooltip` member — a new member of the `popup` family, not a variant of `popup.content`.