Popover
import { Popover } from '@absoluteui/components/popover';import '@absoluteui/components/popover/style.css';Using Angular? See Popover for Angular.
What it does
Section titled “What it does”A non-modal dialog anchored to a trigger button: the WAI-ARIA APG Disclosure pattern for the trigger, plus a non-modal dialog for the popup. The popup renders in the top layer through the Popover API, so overflow containers never clip it. It sits immediately after the trigger in the DOM, so Tab moves from the trigger into the popup and then on through the page. Escape and outside-pointer dismissal go through the shared overlay stack, so only the topmost overlay responds.
When to use
Section titled “When to use”- Contextual content or controls tied to a trigger: filters, quick settings, details, a small form.
- Content the user may interact with (links, buttons, inputs) that should not block the rest of the page.
When not to use
Section titled “When not to use”- Tasks that must be completed or dismissed before continuing: use Dialog.
- Short, non-interactive hints on hover or focus: use Tooltip.
- A list of commands or choices: use Menu or Select.
- Essential content users must not miss: keep it inline on the page.
Examples
Section titled “Examples”Filters popover on a button
Section titled “Filters popover on a button”import { Popover } from '@absoluteui/components/popover';import { Button } from '@absoluteui/components/button';import { Checkbox } from '@absoluteui/components/checkbox';
const trigger = document.createElement('button');trigger.type = 'button';trigger.textContent = 'Filters';Button.enhance(trigger);document.body.append(trigger);const form = document.createElement('div');const onlyActive = document.createElement('div');form.append(onlyActive);Checkbox.create(onlyActive, { label: 'Only active' });Popover.create(trigger, { title: 'Filters', content: form, showCloseButton: true });Keep open while a draft is unsaved
Section titled “Keep open while a draft is unsaved”import { Popover, type PopoverCloseDetail } from '@absoluteui/components/popover';import { Button } from '@absoluteui/components/button';import { Textarea } from '@absoluteui/components/textarea';
const trigger = document.createElement('button');trigger.type = 'button';trigger.textContent = 'Add note';Button.enhance(trigger);document.body.append(trigger);const noteHost = document.createElement('div');const note = Textarea.create(noteHost, { label: 'Note' }).textarea;const popover = Popover.create(trigger, { label: 'Add note', content: noteHost, placement: 'right-start' });popover.element.addEventListener('aui-cancel', (event) => { const { reason } = (event as CustomEvent<PopoverCloseDetail>).detail; if (reason === 'outside-pointer' && note.value !== '') event.preventDefault();});Global defaults
Section titled “Global defaults”import { configureAbsoluteUI } from '@absoluteui/core';
configureAbsoluteUI({ components: { popover: { placement: 'bottom-end', showCloseButton: true } } });import { Popover, type PopoverCloseDetail } from '@absoluteui/components/popover';import '@absoluteui/components/popover/style.css';
const trigger = document.querySelector<HTMLButtonElement>('#share');if (trigger) { const link = document.createElement('input'); link.readOnly = true; link.value = location.href; link.setAttribute('aria-label', 'Link'); const share = Popover.create(trigger, { title: 'Share', content: link, placement: 'bottom-end' }); share.element.addEventListener('aui-close', (event) => { const { reason } = (event as CustomEvent<PopoverCloseDetail>).detail; if (reason === 'escape') trigger.dataset.dismissed = 'true'; });}More usage
Section titled “More usage”import { Popover } from '@absoluteui/components/popover';
const trigger = document.querySelector<HTMLButtonElement>('#filters');if (trigger) Popover.create(trigger, { title: 'Filters', content: 'No filters applied.', showCloseButton: true });Anatomy
Section titled “Anatomy”<button aria-haspopup="dialog" aria-expanded aria-controls=popup> (consumer trigger)<div class="aui-popover" role="dialog" popover="manual" tabindex="-1" aria-labelledby=title | aria-label> <div class="aui-popover__header"> (title and/or close button) <h2 class="aui-popover__title">Title</h2> <button class="aui-button aui-popover__close" aria-label="Close"> (showCloseButton) </div> <div class="aui-popover__body">content</div></div>API reference
Section titled “API reference”Factory
Section titled “Factory”Popover.create(trigger: HTMLElement, options: PopoverOptions): PopoverGlobal configuration key: popover (see Configuration).
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
content |
Node | string |
Popup content; strings render as text. Required. | |
title |
string |
Visible heading; names the popup through aria-labelledby. Either title or label is required. | |
label |
string |
aria-label when there is no title. | |
icons |
Partial<{ close: IconRef }> |
Icon slot overrides. | |
placement |
Placement |
"bottom-start" |
Preferred side and logical alignment; flips and shifts to fit. Globally configurable. |
offset |
number (0..64) |
8 |
Gap between trigger and popup, px. Globally configurable. |
closeOnEscape |
boolean |
true |
Escape requests close (cancelable). Escape is consumed while the popover is topmost either way. Globally configurable. |
closeOnOutsidePointer |
boolean |
true |
Pointer down outside the popup and trigger requests close (cancelable). Globally configurable. |
initialFocus |
'first' | 'container' | 'none' |
"first" |
Where focus goes on open: first tabbable element (falls back to the container), the popup container, or nowhere. Globally configurable. |
showCloseButton |
boolean |
false |
Render a localized close button in the header. Globally configurable. |
direction |
'ltr' | 'rtl' |
"ltr" |
Resolves logical start/end placements. Globally configurable. Inherited from common configuration. |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
element |
HTMLDivElement |
The popup (div.aui-popover[role=dialog]). |
body |
HTMLDivElement |
Content container. |
trigger |
HTMLElement |
The trigger element. |
isOpen |
boolean |
Whether the popup is open. |
options |
Readonly<PopoverOptions> |
Current options. |
Methods
Section titled “Methods”| Method | Description |
|---|---|
open(): void |
Show, position, and move focus per initialFocus. |
close(): void |
Close programmatically (not cancelable). Focus inside the popup returns to the trigger. |
toggle(): void |
Open or close programmatically. |
update(options: Partial<PopoverOptions>): void |
Merge and apply options; repositions when open. |
destroy(): void |
Close if open; remove the popup, trigger ARIA attributes, listeners, and subscriptions. |
Events
Section titled “Events”| Event | Detail | Bubbles | Cancelable | Description |
|---|---|---|---|---|
aui-open |
null |
No | No | After the popup opened (dispatched on the popup element). |
aui-cancel |
{ reason: 'escape' | 'outside-pointer' | 'focus-out' | 'trigger' | 'close-button' } |
No | Yes | Before a user-initiated close; preventDefault keeps it open. |
aui-close |
{ reason: PopoverCloseReason } |
No | No | After the popup closed. |
Content areas
Section titled “Content areas”| Area | Description |
|---|---|
body |
Popover.body (or the content option). |
CSS classes
Section titled “CSS classes”All rules live in the aui.components cascade layer, so application styles override them without specificity battles.
| Class | Purpose |
|---|---|
aui-popover |
Popup element (manual popover, top layer). |
aui-popover--static |
Set when animation is disabled globally. |
aui-popover__header |
Title row with the optional close button. |
aui-popover__title |
Heading (h2). |
aui-popover__close |
Close button. |
aui-popover__body |
Content container. |
Icon slots
Section titled “Icon slots”| Slot | Default icon |
|---|---|
close |
close |
Design tokens
Section titled “Design tokens”Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.
--aui-border-width | --aui-color-border | --aui-color-focus-ring | --aui-color-surface | --aui-color-text | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-md | --aui-font-weight-semibold | --aui-line-height-normal | --aui-line-height-tight | --aui-radius-md | --aui-shadow-md | --aui-space-1 | --aui-space-2 | --aui-space-3 | --aui-space-4
TypeScript definitions
Section titled “TypeScript definitions”type Placement = 'top' | 'bottom' | 'left' | 'right' | `${'top' | 'bottom' | 'left' | 'right'}-${'start' | 'end'}`;type PopoverInitialFocus = 'first' | 'container' | 'none';type PopoverCloseReason = 'escape' | 'outside-pointer' | 'focus-out' | 'trigger' | 'close-button' | 'programmatic';interface PopoverCloseDetail { reason: PopoverCloseReason }interface PopoverOptions extends Partial<PopoverConfig> { content: Node | string; title?: string; label?: string; direction?: 'ltr' | 'rtl'; icons?: Partial<PopoverIcons> }Accessibility
Section titled “Accessibility”Pattern: WAI-ARIA APG Disclosure (trigger) with a non-modal Dialog (popup).
Keyboard
Section titled “Keyboard”| Key | Context | Action |
|---|---|---|
| Enter / Space | Trigger focused | Toggles the popup; on open, focus moves per initialFocus. |
| Tab / Shift+Tab | Popup open | The popup follows the trigger in the tab order; tabbing past its last control (or back before the trigger) closes it (focus-out). |
| Escape | Popup open and topmost, closeOnEscape=true | Emits cancelable aui-cancel, closes, and returns focus to the trigger. |
| Escape | Popup open inside a Dialog | Closes only the popover; a second Escape closes the dialog. |
| Enter / Space | Close button focused | Closes and returns focus to the trigger. |
| Element | Role |
|---|---|
| popup div | dialog (non-modal) |
| title | heading level 2 |
| trigger | button (consumer-provided, native <button> recommended) |
ARIA attributes
Section titled “ARIA attributes”| Attribute | Element | Value |
|---|---|---|
aria-haspopup |
trigger | dialog |
aria-expanded |
trigger | true while open, false otherwise |
aria-controls |
trigger | id of the popup |
aria-labelledby |
popup | id of the title heading (when title is set) |
aria-label |
popup | label option (when there is no title) |
aria-label |
close button | Localized ‘Close’ |
On open, focus moves to the first tabbable element in the popup (initialFocus ‘first’, falling back to the popup), to the popup itself (‘container’), or stays on the trigger (‘none’). Escape and the close button return focus to the trigger; an outside pointer or focus moving elsewhere leaves focus where the user put it; a programmatic close moves focus to the trigger only if it was inside the popup.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”| Criterion | Level | Note |
|---|---|---|
| 1.3.1 Info and Relationships | A | |
| 1.3.2 Meaningful Sequence | A | |
| 2.1.1 Keyboard | A | |
| 2.1.2 No Keyboard Trap | A | |
| 2.4.3 Focus Order | A | |
| 2.4.7 Focus Visible | AA | |
| 4.1.2 Name, Role, Value | A |
Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- The popup is placed immediately after the trigger in the DOM so reading and tab order match the visual relationship (1.3.2, 2.4.3).
- Non-modal: the rest of the page stays interactive and is not inert.
- Top-layer rendering avoids clipping by overflow containers.
- Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.
Behavior
Section titled “Behavior”States
Section titled “States”closed, open, closing (aui-cancel pending).
State transitions
Section titled “State transitions”| From | Trigger | To |
|---|---|---|
| closed | trigger click, Enter, or Space; open() |
open; focus per initialFocus; aui-open |
| open | trigger click | aui-cancel, then closed (trigger) |
| open | Escape while topmost | aui-cancel, then closed (escape); focus to trigger |
| open | pointer down outside popup and trigger | aui-cancel, then closed (outside-pointer); focus not moved |
| open | focus moves outside popup and trigger | aui-cancel, then closed (focus-out) |
| open | close button | aui-cancel, then closed (close-button); focus to trigger |
| open | close(), toggle(), destroy() |
closed (programmatic), not cancelable |
Accessibility notes
Section titled “Accessibility notes”- A missing accessible name is a configuration error.
- DOM order matches the visual relationship, so reading and tab order continue naturally (WCAG 1.3.2, 2.4.3).
- The background stays interactive: this is not a focus trap.
- With
initialFocus: 'first'and no focusable content, focus moves to the popup itself (tabindex="-1"). - The trigger should be a native
<button>. - Manual screen-reader verification is pending (see Known limitations).
Responsive behavior
Section titled “Responsive behavior”Content width up to min(24rem, 100vw - 2rem). The block size is capped at the space available on the chosen side, and the popup scrolls. The positioning engine flips and shifts to stay in the viewport.
Right-to-left
Section titled “Right-to-left”start and end placements are logical. With direction: 'rtl', bottom-start aligns the popup to the trigger’s right edge. Header spacing uses logical properties.
Theming
Section titled “Theming”Colors come from role tokens only. Verified in the light, dark, and high-contrast themes. In forced-colors mode the popup has a CanvasText border. The open fade uses @starting-style and is removed under reduced motion or animation: 'none'.
Global configuration
Section titled “Global configuration”configureAbsoluteUI({ components: { popover: { placement: 'bottom-end', showCloseButton: true } } });Changes apply to existing instances immediately (open popovers are repositioned).
Component configuration
Section titled “Component configuration”Instance options override global and theme defaults. All options are dynamic.
icons.close (default close).
Security
Section titled “Security”Title, label, and string content use textContent; they are never parsed as HTML. Node content is inserted as given, so the consumer owns its safety. CSP-safe: positioning uses the CSSOM.
Data handling
Section titled “Data handling”No persistence, network access, or telemetry.
Performance
Section titled “Performance”Three listeners while closed (trigger click, two focusout) and one configuration subscription. While open: one overlay-stack entry plus resize observers and scroll/resize listeners from the positioning engine, all released on close. Budget: open to positioned within one frame.
Supported data scale
Section titled “Supported data scale”Any number of closed instances per page. Keep one popover open at a time; nesting one level (a popover in a Dialog, or a tooltip in a popover) is tested.
Edge cases
Section titled “Edge cases”- Clicking the trigger while open closes it with reason
trigger(the pointer is treated as inside, so no outside-pointer close happens first). closeOnEscape: falsestill consumes Escape while the popover is topmost, so an enclosing Dialog does not close.- Removing the close button (
update({ showCloseButton: false })) while it has focus moves focus to the popup. - Focus leaving the browser window (no
relatedTarget) does not close the popup.
Errors
Section titled “Errors”| Code | Category | Cause |
|---|---|---|
POPOVER_TRIGGER |
configuration | The trigger is not a connected HTMLElement. |
POPOVER_OPTIONS |
configuration | Missing or invalid content, or neither title nor label. |
CFG_* |
configuration | Invalid placement, offset, initialFocus, or boolean options. |
A failed construction leaves no rendering, trigger attributes, listeners, or subscriptions behind. A failed update() keeps the previous options.
Known limitations
Section titled “Known limitations”- Enter and Space activation relies on the trigger being a native
<button>. - The popup is not portaled; it inherits the trigger’s container styles (such as
dirand font). - The heading is always an
h2. - The close animation is not played.
- Canceling
aui-cancelfor afocus-outclose leaves the popup open while focus is elsewhere. - Screen-reader behavior has not yet been manually verified with NVDA, JAWS, and VoiceOver.
Unsupported scenarios
Section titled “Unsupported scenarios”- Modal behavior (inert background, focus trap): use Dialog.
- HTML strings as content.
- Opening on hover.