Popover
An accessible popup anchored to a trigger for rich, interactive content.
Areia's Popover is built on @data-slot/popover and rendered as an Ilha island when you use Popover. It supports click triggers, dismiss on outside click or Escape, focus management, collision-aware positioning, close buttons, and optional arrows.
Notifications
You are all caught up. Good job!
import ilha from "ilha";
import { Bell } from "lucide";
import { Button, Icon, Popover } from "areia";
export default ilha.render(() => (
<Popover
trigger={
<Button
shape="square"
icon={<Icon icon={Bell} />}
aria-label="Notifications"
/>
}
content={
<>
<Popover.Title>Notifications</Popover.Title>
<Popover.Description>
You are all caught up. Good job!
</Popover.Description>
</>
}
/>
));Import
import { Popover } from "areia";
Usage
Call Popover(...) for an interactive island. Prefer composing Popover.Trigger and Popover.Content as children; the trigger and content props remain available as shortcuts.
Popover Title
Popover content goes here.
import ilha from "ilha";
import { Button, Popover } from "areia";
export default ilha.render(() => (
<Popover>
<Popover.Trigger>
<Button>Open</Button>
</Popover.Trigger>
<Popover.Content>
<Popover.Title>Popover Title</Popover.Title>
<Popover.Description>
Popover content goes here.
</Popover.Description>
</Popover.Content>
</Popover>
));For static server-rendered markup or custom initialization, use Popover.Static(...). For most application usage, call Popover(...).
Popover vs Tooltip
| Tooltip | Popover | |
|---|---|---|
| Purpose | Short, non-interactive labels | Rich, interactive content containers |
| Content | Brief text | Links, buttons, forms, custom layouts |
| Trigger | Hover or focus | Click |
| ARIA | role="tooltip" | aria-haspopup="dialog" on the trigger |
| Focus | Does not move focus into popup | Moves focus into the popover when opened |
Use Tooltip to label an icon button or add a short explanation. Use Popover when users need to interact with content inside the popup.
Examples
Basic Popover
Popover Title
This is a basic popover with a title and description.
import ilha from "ilha";
import { Button, Popover } from "areia";
export default ilha.render(() => (
<Popover
trigger={<Button>Open Popover</Button>}
content={
<>
<Popover.Title>Popover Title</Popover.Title>
<Popover.Description>
This is a basic popover with a title and description.
</Popover.Description>
</>
}
/>
));With Close Button
Use Popover.Close inside the content to close the popup.
Settings
Configure your preferences below.
import ilha from "ilha";
import { Button, Popover } from "areia";
export default ilha.render(() => (
<Popover
trigger={<Button>Open Settings</Button>}
content={
<div class="flex flex-col gap-3">
<div>
<Popover.Title>Settings</Popover.Title>
<Popover.Description>
Configure your preferences below.
</Popover.Description>
</div>
<Popover.Close class="w-max rounded-md bg-areia-control-background px-3 py-1.5 text-sm ring ring-areia-control-border">
Close
</Popover.Close>
</div>
}
/>
));Positioning
Use side to control where the popover appears relative to the trigger.
Bottom
Popover on bottom.
Top
Popover on top.
Left
Popover on left.
Right
Popover on right.
import ilha from "ilha";
import { Button, Popover } from "areia";
export default ilha.render(() => (
<div class="grid grid-cols-2 gap-3">
<Popover
side="bottom"
trigger={<Button>Bottom</Button>}
content={
<>
<Popover.Title>Bottom</Popover.Title>
<Popover.Description>
Popover on bottom.
</Popover.Description>
</>
}
/>
<Popover
side="top"
trigger={<Button>Top</Button>}
content={
<>
<Popover.Title>Top</Popover.Title>
<Popover.Description>
Popover on top.
</Popover.Description>
</>
}
/>
<Popover
side="left"
trigger={<Button>Left</Button>}
content={
<>
<Popover.Title>Left</Popover.Title>
<Popover.Description>
Popover on left.
</Popover.Description>
</>
}
/>
<Popover
side="right"
trigger={<Button>Right</Button>}
content={
<>
<Popover.Title>Right</Popover.Title>
<Popover.Description>
Popover on right.
</Popover.Description>
</>
}
/>
</div>
));Alignment
Use align to align the popover along the selected side.
import ilha from "ilha";
import { Button, Popover } from "areia";
export default ilha.render(() => (
<div class="flex items-center gap-3">
<Popover
align="start"
trigger={<Button>Start</Button>}
content="Start aligned"
/>
<Popover
align="center"
trigger={<Button>Center</Button>}
content="Center aligned"
/>
<Popover
align="end"
trigger={<Button>End</Button>}
content="End aligned"
/>
</div>
));Custom Content
Popovers can contain rich content including avatars, buttons, links, and forms.
Jane Doe
jane@example.com
import ilha from "ilha";
import { Button, Popover } from "areia";
export default ilha.render(() => (
<Popover
trigger={<Button>User Profile</Button>}
content={
<div class="flex min-w-56 flex-col gap-4">
<div class="flex items-center gap-3">
<div class="flex size-9 items-center justify-center rounded-full bg-areia-surface-muted text-sm font-medium">
JD
</div>
<div>
<Popover.Title>Jane Doe</Popover.Title>
<Popover.Description>
jane@example.com
</Popover.Description>
</div>
</div>
<div class="flex gap-2">
<Button size="sm">Profile</Button>
<Popover.Close class="rounded-md px-2 text-sm text-areia-subtle hover:bg-areia-control-hover">
Sign Out
</Popover.Close>
</div>
</div>
}
/>
));Without Arrow
Set arrow: false to hide the decorative arrow.
import ilha from "ilha";
import { Button, Popover } from "areia";
export default ilha.render(() => (
<Popover
arrow={false}
trigger={<Button>Open</Button>}
content="This popover has no arrow."
/>
));Default Open
Use defaultOpen to render the popover open on mount.
import ilha from "ilha";
import { Button, Popover } from "areia";
export default ilha.render(() => (
<Popover
defaultOpen
trigger={<Button>Open by default</Button>}
content={
<>
<Popover.Title>Hello</Popover.Title>
<Popover.Description>
This starts open.
</Popover.Description>
</>
}
/>
));Dismiss Behavior
Disable outside-click dismissal with closeOnClickOutside: false. Escape dismissal can be controlled with closeOnEscape.
Persistent popover
Click the close button to dismiss this popover.
import ilha from "ilha";
import { Button, Popover } from "areia";
export default ilha.render(() => (
<Popover
closeOnClickOutside={false}
trigger={<Button>Persistent</Button>}
content={
<>
<Popover.Title>Persistent popover</Popover.Title>
<Popover.Description>
Click the close button to dismiss this popover.
</Popover.Description>
<Popover.Close class="mt-3 rounded-md bg-areia-control-background px-3 py-1.5 text-sm ring ring-areia-control-border">
Close
</Popover.Close>
</>
}
/>
));Custom Trigger
Use trigger when you need complete control over the trigger markup. The custom trigger must include data-slot="popover-trigger".
import ilha from "ilha";
import { Popover } from "areia";
export default ilha.render(() => (
<Popover
trigger={
<span
data-slot="popover-trigger"
tabindex="0"
class="inline-flex rounded-full bg-areia-surface-muted px-2 py-1 text-sm text-areia-default"
>
Beta
</span>
}
content="This feature is currently in beta."
/>
));Prop Shortcut
Use trigger and content for concise popovers. Areia generates the trigger and content slots for you.
Shortcut API
Use props when you do not need to customize the slot structure.
import ilha from "ilha";
import { Popover } from "areia";
export default ilha.render(() => (
<Popover
trigger={<button>Open</button>}
triggerAs="button"
content={
<>
<Popover.Title>Shortcut API</Popover.Title>
<Popover.Description>
Use props when you do not need to customize the slot
structure.
</Popover.Description>
</>
}
/>
));API Reference
Popover
Popover is an Ilha island. It accepts standard HTML div attributes plus @data-slot/popover options.
| Prop | Type | Default | Description |
|---|---|---|---|
children | unknown | - | Composed popover slots, or trigger content when no Popover.Content child is present. |
content | unknown | - | Popover panel content. |
trigger | unknown | - | Custom trigger markup. Must include data-slot="popover-trigger". |
triggerAs | "button" | "span" | "div" | "a" | "span" | Tag used for the generated trigger. |
arrow | boolean | true | Whether to render the decorative arrow. |
class | string | - | Additional CSS classes applied to the root. |
className | string | - | Alias for class. |
triggerClass | string | - | Additional CSS classes applied to the generated trigger. |
triggerClassName | string | - | Alias for triggerClass. |
contentClass | string | - | Additional CSS classes applied to the content. |
contentClassName | string | - | Alias for contentClass. |
defaultOpen | boolean | false | Initial open state. |
side | "top" | "right" | "bottom" | "left" | "bottom" | Preferred side relative to the trigger. |
align | "start" | "center" | "end" | "center" | Preferred alignment on the side axis. |
sideOffset | number | 8 | Distance from the trigger in pixels. |
alignOffset | number | 0 | Offset from the alignment edge in pixels. |
avoidCollisions | boolean | true | Flip or shift to stay inside the viewport. |
collisionPadding | number | 8 | Viewport edge padding used for collision handling. |
portal | boolean | true | Portal content to document.body while open. |
closeOnClickOutside | boolean | true | Close when clicking outside the popover. |
closeOnEscape | boolean | true | Close when pressing Escape. |
onOpenChange | (open: boolean) => void | - | Called when open state changes. |
onPortalMounted | (container: HTMLElement) => void | - | After portaled content mounts on open (Ilha bridge escape hatch). See Dialog docs, Ilha + portaled overlays. |
Popover.Trigger
Generated trigger element.
| Prop | Type | Default | Description |
|---|---|---|---|
as | "button" | "span" | "div" | "a" | "span" | Trigger tag name. |
children | unknown | - | Trigger content. |
class | string | - | Additional classes. |
className | string | - | Alias for class. |
Popover.Content
Popover panel. Controls positioning through placement props.
| Prop | Type | Default | Description |
|---|---|---|---|
children | unknown | - | Panel content. |
arrow | boolean | true | Whether to render the arrow. |
side | "top" | "right" | "bottom" | "left" | "bottom" | Preferred side styling. |
align | "start" | "center" | "end" | "center" | Preferred alignment. |
class | string | - | Additional classes. |
className | string | - | Alias for class. |
Popover.Title and Popover.Description
Popover.Title renders an h3 styled for popover headings. Popover.Description renders a subdued paragraph.
Popover.Close
Close control. The data-slot controller closes the popover when this element is clicked.
| Prop | Type | Default | Description |
|---|---|---|---|
as | "button" | "span" | "div" | "a" | "button" | Close tag name. |
children | unknown | "Close" | Close content. |
class | string | - | Additional classes. |
className | string | - | Alias for class. |
Accessibility
Behavior
- Trigger click toggles the popover.
- Pressing
Escapecloses the popover and returns focus to the trigger. - Clicking outside closes the popover by default.
- Focus moves into the popover when opened.
Popover.Closeprovides an explicit close target inside interactive content.
ARIA
The controller automatically handles:
aria-haspopup="dialog"on the trigger.aria-controlslinking the trigger to the content.aria-expandedon the trigger.- Stable generated IDs for content when needed.
Keyboard Navigation
| Key | Action |
|---|---|
Enter / Space | Toggle popover on the trigger. |
Escape | Close the popover. |