Skip to the content

Kongens Nytorv

Metro M1, M2 and M3.

Props

NameTypeDefaultDescription
title*ReactNode—The popover's title, in sentence case, such as "About the data". Required: it names the popover for screen readers.
descriptionReactNode—One sentence under the title that says what the popover is for, read out with its name when it opens. Phrasing content only: it renders in a <p>.
childrenReactNode—The popover's body, under the description: a few sentences or controls.
triggerReactElement<unknown, JSXElementConstructor<any> | string>—The element that opens the popover, such as a Button, rendered in place of the popover; the popover sits against it, and focus returns to it when the popover closes. Leave it out for a popover opened only through open, placed by slotProps.positioner.anchor.
openboolean—Whether the popover is open. Set it to control the popover, with onClose to hear when it asks to close; leave it out for a popover its trigger opens.
defaultOpenbooleanfalseWhether the popover is open at first, when open is left out.
onOpen(event: Event, details: Popover.Root.ChangeEventDetails) => void—Called when the popover asks to open, from its trigger. details is Base UI's, with its reason and cancel().
onClose(event: Event, reason: Popover.Root.ChangeEventReason, details: Popover.Root.ChangeEventDetails) => void—Called when the popover asks to close: reason is 'escape-key', 'outside-press', 'close-press', 'focus-out' or another of Base UI's reasons, and details.cancel() keeps it open.
classNamestring—Reaches the root, the popup. className is merged after pn-Popover.
styleCSSProperties—Reaches the root, the popup. className is merged after pn-Popover.
refRef<HTMLDivElement>—Reaches the root, the popup. className is merged after pn-Popover.
closeLabelstringThe locale'sOverrides "Close", the close button's name.
slotProps{ trigger?: Props<unknown> | undefined; portal?: PopoverPortalProps | undefined; positioner?: PopoverPositionerProps | undefined; title?: PopoverTitleProps | undefined; description?: PopoverDescriptionProps | undefined; close?: Omit<PopoverCloseProps, "disabled"> | undefined; }—Props for each part: trigger, portal, positioner, title, description, close.
title (required)ReactNode, default —
The popover's title, in sentence case, such as "About the data". Required: it names the popover for screen readers.
descriptionReactNode, default —
One sentence under the title that says what the popover is for, read out with its name when it opens. Phrasing content only: it renders in a <p>.
childrenReactNode, default —
The popover's body, under the description: a few sentences or controls.
triggerReactElement<unknown, JSXElementConstructor<any> | string>, default —
The element that opens the popover, such as a Button, rendered in place of the popover; the popover sits against it, and focus returns to it when the popover closes. Leave it out for a popover opened only through open, placed by slotProps.positioner.anchor.
openboolean, default —
Whether the popover is open. Set it to control the popover, with onClose to hear when it asks to close; leave it out for a popover its trigger opens.
defaultOpenboolean, default false
Whether the popover is open at first, when open is left out.
onOpen(event: Event, details: Popover.Root.ChangeEventDetails) => void, default —
Called when the popover asks to open, from its trigger. details is Base UI's, with its reason and cancel().
onClose(event: Event, reason: Popover.Root.ChangeEventReason, details: Popover.Root.ChangeEventDetails) => void, default —
Called when the popover asks to close: reason is 'escape-key', 'outside-press', 'close-press', 'focus-out' or another of Base UI's reasons, and details.cancel() keeps it open.
classNamestring, default —
Reaches the root, the popup. className is merged after pn-Popover.
styleCSSProperties, default —
Reaches the root, the popup. className is merged after pn-Popover.
refRef<HTMLDivElement>, default —
Reaches the root, the popup. className is merged after pn-Popover.
closeLabelstring, default The locale's
Overrides "Close", the close button's name.
slotProps{ trigger?: Props<unknown> | undefined; portal?: PopoverPortalProps | undefined; positioner?: PopoverPositionerProps | undefined; title?: PopoverTitleProps | undefined; description?: PopoverDescriptionProps | undefined; close?: Omit<PopoverCloseProps, "disabled"> | undefined; }, default —
Props for each part: trigger, portal, positioner, title, description, close.

Use it

import { Popover } from '@mylder/perron'

Ask your agent

get_reference({ library: "base-ui", component: "Popover" })

Keyboard and ARIA

From the Base UI reference for Popover and the installed PopoverRoot, PopoverTrigger, PopoverPositioner, PopoverPopup, PopoverClose and the focus manager they build on. "From" says who does it.

KeyDoesFrom
Enter, Space on the triggerOpen the popover, calling onOpen with reason 'trigger-press', and move focus to the first tabbable element in the popup, the close button, unless initialFocus says otherwiseThe browser activates the button; Base UI opens and moves focus
TabMoves focus through the popup's tabbable elements in reading order, the close button, then the content's; from the last, on to the element after the trigger, closing the popover with 'focus-out'Base UI
Shift+TabMoves focus back through the popup's tabbable elements; from the first, back to the trigger, leaving the popover openBase UI
EscapeCloses the popover, calling onClose with reason 'escape-key', and returns focus to the triggerBase UI
Enter, Space on the close buttonClose the popover, calling onClose with reason 'close-press', and return focus to the triggerThe browser activates the button; Base UI closes

A press outside the popup and the trigger closes the popover too, with reason 'outside-press', and leaves focus where the press put it.

ElementRoleAccessible name fromStates announced
triggerbutton, native, or the caller's element's ownIts own content, from the calleraria-haspopup="dialog", aria-expanded, from Base UI
positionerpresentation, from Base UInonenone
rootdialog, from Base UIaria-labelledby the title; described by the description through aria-describedbynone: it is not modal, so nothing outside it is hidden
titleheading, level 2, from the native <h2>Its text, the caller's titlenone
closebutton, nativeThe owned "Close", or closeLabel, in the CloseButton's VisuallyHidden spannone: it is never disabled (CR-6)
header, contentnone: plain <div>snonenone

What axe won't catch, from the contract, each with how this component meets it:

  • Everything the eye is told, the ear is told, for every shape each prop accepts: the popover is announced with its title as its name and its description, the close button's aria-hidden icon has the owned "Close" as its text, and that the popover belongs to its trigger is told by the trigger's aria-expanded and aria-controls.
  • Every landmark and named region has an accessible name: the popover is named by its own heading, the title, through aria-labelledby, and title is required.
  • Every default label can be overridden: closeLabel.
  • Every shape the type accepts renders, in every variant: title, description and children render a string, an element and a list alike; a long word in the title, the description or the content wraps (CR-9).
  • A hidden copy kept for animation is aria-hidden, and a collapsed region is inert: none here; the closed popover renders nothing, and Base UI makes the positioner inert while it closes.
  • Native semantics first: Base UI's dialog role, a native <h2> for the title, native <button>s for the trigger and the close button, and no ARIA of Perron's own.
  • Focus moves in reading order and returns on close: the header comes first in the DOM, then the description and the content, as on screen; Base UI puts the popup in the tab order right after the trigger; opening moves focus into the popup, and closing by Esc or the close button returns it to the trigger, also for a popover open at first render (CR-5).
  • Every target is at least size.target-min square: the close button, size.control (34 px) square.
  • Loops stop when the system asks for less motion: none here; the popover has no loop and no transition (CR-10).

Owned strings

KeyEnglishOverrideSeen or heard where
close"Close"closeLabelclose, heard as the close button's name from the CloseButton's VisuallyHidden span; never seen

close is the key packages/perron/src/locale/en.ts already has, "A button that closes a dialog, panel or notice", so Popover adds no key. Every other word is the caller's, through title, description, children and trigger.

Do and don't

For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.

RuleLevelDoDon'tChecked by
DD-1mustGive every popover a short title in sentence case, such as "About the data" or "Map layers", which names itLeave the title empty or hide it: the popover then has no namereview
DD-2mustPass the button that opens the popover as trigger, so the popover sits against it, is announced as opening and gives focus backOpen a popover from a button placed beside it with open alone, when a trigger would do: the button loses aria-haspopup and aria-expanded, and the popover has nothing to sit againstreview
DD-3mustSet slotProps.trigger.nativeButton to false when trigger is not a <button>, as Base UI warnsPass a link as the trigger: a link goes somewhere, and a popover opens over the screenreview
DD-4shouldKeep it to a few sentences or a few controls that leave the map usableAsk a question that must be answered, or put a long form in it: use a Dialog, which holds focus until it is answeredreview
DD-5mustKeep the description to one sentence, and put lists and controls in childrenPut block content, such as a list, in description: it renders in a <p>review
DD-6shouldOpen it on a press, and add slotProps.trigger.openOnHover only beside that, as Accessibility asks of anything shown on hoverUse a popover for a few words on hover over the map: that is a MapTooltipreview
DD-7mustIn a Toolbar, pass a ToolbarButton as trigger, and disable it with slotProps.trigger.disabled: it keeps its place in the arrow-key order, and is announced as disabledPass a Button as the trigger in a toolbar, or set disabled on the ToolbarButton itself: the arrow keys skip a Button, which adds a tab stop of its own, and a ToolbarButton disabled on its own looks disabled but is announced as enabledreview
DD-8mustLeave the close button's label to the locale, or override it with closeLabelHide the close button with a className: touch screen readers leave the popover through itreview
DD-9shouldLet the popover take z-index.dialog and Base UI's placement, and change the side through slotProps.positionerSet a z-index, a position or a transform on the popup: it would fall under another overlay or leave its triggerreview