Popover
A small panel anchored to the control that opened it — pick a day, glance at a booking. The page stays live around it; a press outside or Escape closes it. A white squircle 8px off its trigger, built on the vendored Base UI popover.
Playground
import { Button } from "@/components/shared/button";
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@/components/shared/popover";
<Popover>
<PopoverTrigger render={<Button variant="secondary">Walk-ins</Button>} />
<PopoverContent>
<PopoverHeader>
<PopoverTitle>Walk-ins</PopoverTitle>
<PopoverDescription>Martina takes walk-ins between bookings until 16:00.</PopoverDescription>
</PopoverHeader>
</PopoverContent>
</Popover>Date picker
The day view's picker. A flush popover hugs the Calendar, which brings its own padding; aligned to the trigger's start edge, it closes as soon as a day is picked.
import { Button } from "@/components/shared/button";
import { Calendar } from "@/components/shared/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/shared/popover";
import { CalendarIcon } from "@hugeicons/core-free-icons";
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger
render={
<Button variant="secondary" leadingIcon={CalendarIcon} className="min-w-48 justify-start">
{dayLabel(date)}
</Button>
}
/>
<PopoverContent flush align="start">
<Calendar
mode="single"
selected={date}
onSelect={(day) => {
if (!day) return;
setDate(day);
setOpen(false);
}}
/>
</PopoverContent>
</Popover>Details
A booking at a glance: a header names it, a line or two says the rest, and small buttons offer what comes next. PopoverClose closes the popover as it acts.
import { Button } from "@/components/shared/button";
import {
Popover,
PopoverClose,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@/components/shared/popover";
<Popover>
<PopoverTrigger render={<Button variant="secondary">10:00 · Skin fade</Button>} />
<PopoverContent>
<PopoverHeader>
<PopoverTitle>Skin fade</PopoverTitle>
<PopoverDescription>Friday 10:00–10:45 · with Martina</PopoverDescription>
</PopoverHeader>
<p>Jana Nováková · +421 905 123 456</p>
<div className="flex gap-2">
<PopoverClose render={<Button variant="secondary" size="sm">Reschedule</Button>} />
<PopoverClose render={<Button variant="ghost" size="sm">Cancel booking</Button>} />
</div>
</PopoverContent>
</Popover>Placement
Below the trigger by default. side picks another edge and align slides it along; near the screen's edge it flips and shifts to stay in view on its own.
<PopoverContent side="top">…</PopoverContent>
<PopoverContent side="right">…</PopoverContent>
<PopoverContent>…</PopoverContent>
<PopoverContent side="left">…</PopoverContent>API reference
PopoverContent
| Prop | Type | Default | Description |
|---|---|---|---|
| side | "top" | "right" | "bottom" | "left" | "bottom" | The trigger's edge it opens from. Flips when there's no room. |
| align | "start" | "center" | "end" | "center" | Where it sits along that edge. |
| sideOffset | number | 8 | Pixels between trigger and panel. |
| alignOffset | number | 0 | Pixels to shift it along the edge. |
| flush | boolean | false | No padding, width of its content — for a Calendar or a list that brings its own. |
| className | string | — | Layout only — a width such as w-80 or w-auto. Never restyle the surface here. |
Popover
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | — | Controlled open state; pair it with onOpenChange — e.g. to close once a day is picked. |
| onOpenChange | (open: boolean) => void | — | Called on every open and close — trigger, PopoverClose, Escape, a press outside. |
| defaultOpen | boolean | false | Open on first render, uncontrolled. |
| modal | boolean | "trap-focus" | false | Lock the page behind it. Rarely right — reach for a Dialog instead. |
Parts
| Prop | Type | Default | Description |
|---|---|---|---|
| PopoverTrigger | render | — | Opens and closes it. Give it a Button: render={<Button>…</Button>}. |
| PopoverHeader | div | — | Stacks the title over the description. |
| PopoverTitle | h2 | — | Names the popover for assistive tech. |
| PopoverDescription | p | — | A quiet line under the title. |
| PopoverClose | render | — | Closes it from inside. Give it a Button: render={<Button size="sm">Done</Button>}. |
Guidelines
- A popover belongs to its trigger: what's in it is about that control. A task that stands on its own — a form, a decision — is a Dialog.
- Keep it small: a picker, a few lines, at most two small actions. If it needs to scroll, it wants to be a page or a dialog.
- The trigger says what opens — the chosen day, the booking's time and service — not “More” or an unlabelled icon.
- Close it the moment its job is done: a picked day, a chosen action. Don't make people dismiss it by hand.
- Never open a popover from inside another. For choices from a list, use Select.