A popover is an interactive mini-dialog floating element that displays information related to an anchor element when the element is clicked.
Essentials
An accessible popover component has the following qualities:
- Dynamic anchor positioning: The popover is positioned next to its reference element, remaining anchored to it while avoiding collisions.
- Events: When the reference element is clicked, it toggles the popover open or closed.
- Dismissal: When the user presses the
esc
key or outside the popover while it is open, it closes. - Role: The elements are given relevant role and ARIA attributes to be accessible to screen readers.
- Focus management: Focus is managed for non-modal or modal behavior.
Examples
Both of these examples have sections explaining them in-depth below.
Basic popover
This example demonstrates how to create a popover for use in a single instance to familiarize yourself with the fundamentals.
Let’s walk through the example:
Open state
isOpen
determines whether or not the popover is
currently open on the screen. It is used for conditional
rendering.
useFloating Hook
The useFloating()
Hook provides positioning and context
for our popover. We need to pass it some information:
open
: The open state from ouruseState()
Hook above.onOpenChange
: A callback function that will be called when the popover is opened or closed. We’ll use this to update ourisOpen
state.middleware
: Import and pass middleware to the array that ensure the popover remains on the screen, no matter where it ends up being positioned.whileElementsMounted
: Ensure the popover remains anchored to the reference element by updating the position when necessary, only while both the reference and floating elements are mounted for performance.
Interaction Hooks
Interaction Hooks return objects containing keys of props that enable the popover to be opened, closed, or accessible to screen readers.
Using the context
that was returned from the Hook,
call the interaction Hooks:
useClick()
adds the ability to toggle the popover open or closed when the reference element is clicked.useDismiss()
adds the ability to dismiss the popover when the user presses theesc
key or presses outside of the popover.useRole()
adds the correct ARIA attributes for adialog
to the popover and reference elements.
Finally, useInteractions()
merges all of their props into
prop getters which can be used for rendering.
Rendering
Now we have all the variables and Hooks set up, we can render out our elements.
{...getReferenceProps()}
/{...getFloatingProps()}
spreads the props from the interaction Hooks onto the relevant elements. They contain props likeonClick
,aria-expanded
, etc.<FloatingFocusManager />
is a component that manages focus of the popover for non-modal or modal behavior. It should directly wrap the floating element and only be rendered when the popover is also rendered.FloatingFocusManager
docs.
Modal and non-modal behavior
In the above example we used non-modal focus management, but the focus management behavior of a popover can be either modal or non-modal. They are differentiated as follows:
Modal
- The popover and its contents are the only elements that can receive focus. When the popover is open, the user cannot interact with the rest of the page (nor can screen readers) until the popover is closed.
- Needs an explicit close button (though, it can be visually hidden).
This behavior is the default:
Non-modal
- The popover and its contents can receive focus, but the user can still interact with the rest of the page too.
- When tabbing outside of it, the popover automatically closes when it loses focus and the next focusable element in the natural DOM order receives focus.
- Does not need an explicit close button.
This behavior can be configured with the modal
prop
like so:
Reusable popover component
It is better to create a reusable component API that can be used in a variety of different scenarios more easily. We can place all of our Hooks into a single custom Hook for better reusability, which is then used by a controller component which encapsulates the state.
The reusable component can:
- Be uncontrolled or controlled
- Accept any element as the
<PopoverTrigger />
- Read the open state to change styles
Controller component
<Popover />
This is the controller component that manages the popover’s state and provides the API to the rest of the components.
Render components
These components read the context provided by the root Popover component and render the appropriate elements.
The components must be wrapped in forwardRef()
to allow
refs, and should merge the refs to ensure all refs are preserved
and forwarded to the element. Props are also merged to prevent
overwriting.
<PopoverTrigger />
is the trigger button the popover is attached to. This accepts anasChild
prop if you want to attach it to a custom element. It also has adata-state
attached to style based on the open/closed state.<PopoverContent />
is the popover element, which can contain any children (React nodes).<PopoverHeading />
(optional) is the heading element for the popover.<PopoverDescription />
(optional) is the description element for the popover.<PopoverClose />
(optional) is the close button for the popover when using modal focus management.