Presentation
Popover
Show a small task or more detail beside the control it belongs to.
"use client"
import { Button } from "macbetter/button"
import { Field, FieldLabel } from "macbetter/field"
import {
Popover,
PopoverClose,
PopoverContent,
PopoverDescription,
PopoverTitle,
PopoverTrigger,
} from "macbetter/popover"
import { TextField } from "macbetter/text-field"
import { ShareIcon } from "lucide-react"
export default function Example() {
return (
<Popover presentation="auto">
<PopoverTrigger render={<Button />}>
<ShareIcon aria-hidden="true" />
Share
</PopoverTrigger>
<PopoverContent>
<PopoverTitle>Share “Q3 Roadmap”</PopoverTitle>
<PopoverDescription>Anyone with the link can view it.</PopoverDescription>
<div style={{ display: "grid", gap: 12, marginTop: 10, minWidth: 280 }}>
<Field>
<FieldLabel>Link</FieldLabel>
<TextField readOnly defaultValue="macbetter.dev/d/q3-roadmap" />
</Field>
<div style={{ display: "flex", justifyContent: "flex-end" }}>
<PopoverClose render={<Button variant="filled" />}>Copy Link</PopoverClose>
</div>
</div>
</PopoverContent>
</Popover>
)
}When to use it
Use a popover for something brief that belongs to one control: sharing a link, adjusting a few options, explaining a setting. It closes when people click elsewhere, so don’t put work in it that would be painful to lose. For a longer task, use a Dialog; for a list of commands, a Menu; for a few words on hover, a Tooltip.
Usage
import {
Popover,
PopoverClose,
PopoverContent,
PopoverDescription,
PopoverTitle,
PopoverTrigger,
} from "macbetter/popover"PopoverContent draws its arrow and grows out of the trigger. It opens below the trigger and flips or shifts when there isn’t room; the arrow keeps pointing at the trigger. Give it a PopoverTitle to name it.
Placement
side chooses the edge (top, right, bottom, left) and align the alignment along it. Pass arrow={false} for a plain panel.
"use client"
import { Button } from "macbetter/button"
import { Popover, PopoverContent, PopoverDescription, PopoverTitle, PopoverTrigger } from "macbetter/popover"
import { InfoIcon } from "lucide-react"
export default function Example() {
return (
<Popover>
<PopoverTrigger render={<Button variant="plain" shape="circle" aria-label="About sync" />}>
<InfoIcon aria-hidden="true" />
</PopoverTrigger>
<PopoverContent side="right">
<PopoverTitle>iCloud sync</PopoverTitle>
<PopoverDescription>Changes appear on your other devices within a few seconds.</PopoverDescription>
</PopoverContent>
</Popover>
)
}On a phone
iPhone has no room for a floating panel. Pass presentation="auto" to the root and it becomes a sheet you can swipe away on small touch screens, while iPad and the Mac keep the popover. The Share example at the top of this page does this: open it on a phone to see the sheet.
Platform behavior
- macOS. Thick material with 12pt corners, 13pt text, and the arrow pointing at the control. It scales up from the trigger on a quick spring and fades out faster than it came in.
- iPadOS. The same shape with larger corners, 17pt text and touch-size controls.
- iOS. Popovers are rare on iPhone; use
presentation="auto"to get a bottom sheet with a grabber there. - The arrow blurs the page behind it just as the panel does, so the two read as one surface.
Accessibility
- The content has the
dialogrole, named by its title and described by its description. - Focus moves into it on open. Escape, a press outside, or focus leaving it closes it, and focus returns to the trigger.
modaltraps focus and makes the page inert when the popover holds a task that shouldn’t be interrupted.
API
Popover
Takes every ui-sdk Popover root prop, like open, onOpenChange, modal, openOnHover and presentation.
PopoverContent
| Prop | Type | Default | Description |
|---|---|---|---|
arrow | boolean | true | Draws the arrow that points at the trigger. |
side | "top" | "right" | "bottom" | "left" | "bottom" | The edge of the trigger it opens on. |
align | "start" | "center" | "end" | "center" | How it lines up along that edge. |
sideOffset | number | 12 with an arrow, 6 without | The gap to the trigger. |
arrowPadding | number | 14 | Keeps the arrow clear of the corners. |
PopoverTrigger, PopoverTitle, PopoverDescription and PopoverClose take ui-sdk’s props. Render a macbetter Button with render={<Button />}. createPopoverController is re-exported from ui-sdk.
| Data attribute | On | Meaning |
|---|---|---|
data-side, data-align | .mb-popover, .mb-popover-arrow | Where it landed |
data-arrow | .mb-popover | It has an arrow |
data-presentation | .mb-popover | popover or sheet |
data-starting-style, data-ending-style | .mb-popover | Entering and leaving |
| CSS variable | Default |
|---|---|
--mb-popover-radius | --mb-radius-lg on macOS, --mb-popup-radius on touch |
--mb-popover-padding | 14px on macOS, 16px on touch |
--mb-popover-max-width | 320px on macOS, 360px on touch |
--transform-origin, --arrow-offset | Set by ui-sdk |