Skip to content

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

TooltipPopover
PurposeShort, non-interactive labelsRich, interactive content containers
ContentBrief textLinks, buttons, forms, custom layouts
TriggerHover or focusClick
ARIArole="tooltip"aria-haspopup="dialog" on the trigger
FocusDoes not move focus into popupMoves 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.

PropTypeDefaultDescription
childrenunknown-Composed popover slots, or trigger content when no Popover.Content child is present.
contentunknown-Popover panel content.
triggerunknown-Custom trigger markup. Must include data-slot="popover-trigger".
triggerAs"button" | "span" | "div" | "a""span"Tag used for the generated trigger.
arrowbooleantrueWhether to render the decorative arrow.
classstring-Additional CSS classes applied to the root.
classNamestring-Alias for class.
triggerClassstring-Additional CSS classes applied to the generated trigger.
triggerClassNamestring-Alias for triggerClass.
contentClassstring-Additional CSS classes applied to the content.
contentClassNamestring-Alias for contentClass.
defaultOpenbooleanfalseInitial 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.
sideOffsetnumber8Distance from the trigger in pixels.
alignOffsetnumber0Offset from the alignment edge in pixels.
avoidCollisionsbooleantrueFlip or shift to stay inside the viewport.
collisionPaddingnumber8Viewport edge padding used for collision handling.
portalbooleantruePortal content to document.body while open.
closeOnClickOutsidebooleantrueClose when clicking outside the popover.
closeOnEscapebooleantrueClose 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.

PropTypeDefaultDescription
as"button" | "span" | "div" | "a""span"Trigger tag name.
childrenunknown-Trigger content.
classstring-Additional classes.
classNamestring-Alias for class.

Popover.Content

Popover panel. Controls positioning through placement props.

PropTypeDefaultDescription
childrenunknown-Panel content.
arrowbooleantrueWhether to render the arrow.
side"top" | "right" | "bottom" | "left""bottom"Preferred side styling.
align"start" | "center" | "end""center"Preferred alignment.
classstring-Additional classes.
classNamestring-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.

PropTypeDefaultDescription
as"button" | "span" | "div" | "a""button"Close tag name.
childrenunknown"Close"Close content.
classstring-Additional classes.
classNamestring-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

KeyAction
Enter / SpaceToggle popover on the trigger.
EscapeClose the popover.