Skip to content
Areia
Esc
navigateopen⌘Jpreview
On this page

Popover

An accessible popup anchored to a trigger for rich, interactive content.

Source Code

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.

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.

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

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.

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.

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.

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.

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.

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 Escape closes the popover and returns focus to the trigger.
  • Clicking outside closes the popover by default.
  • Focus moves into the popover when opened.
  • Popover.Close provides an explicit close target inside interactive content.

ARIA

The controller automatically handles:

  • aria-haspopup="dialog" on the trigger.
  • aria-controls linking the trigger to the content.
  • aria-expanded on the trigger.
  • Stable generated IDs for content when needed.

Keyboard Navigation

Key Action
Enter / Space Toggle popover on the trigger.
Escape Close the popover.

Was this page helpful?