website/pages/docs/popover.mdx
<div className="mt-6">
<img
className="select-none"
src="/popover.svg"
alt="Popover"
width={729}
height={200}
draggable={false}
/>
</div>
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](https://codesandbox.io/s/withered-shadow-ot38mm?file=/src/App.tsx)
- [Reusable popover component](https://codesandbox.io/s/distracted-swirles-jo1pvu?file=/src/App.tsx)
## Basic popover
[CodeSandbox demo](https://codesandbox.io/s/withered-shadow-ot38mm?file=/src/App.tsx)
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
```js {4}
import {useState} from 'react';
function Popover() {
const [isOpen, setIsOpen] = useState(false);
}
```
`isOpen{:.const}` determines whether or not the popover is
currently open on the screen. It is used for conditional
rendering.
### useFloating Hook
The `useFloating(){:js}` Hook provides positioning and context
for our popover. We need to pass it some information:
- `open{:.key}`: The open state from our `useState(){:js}` Hook
above.
- `onOpenChange{:.key}`: A callback function that will be called
when the popover is opened or closed. We'll use this to update
our `isOpen{:.const}` state.
- `middleware{:.key}`: Import and pass middleware to the array
that ensure the popover remains on the screen, no matter where
it ends up being positioned.
- `whileElementsMounted{:.key}`: 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.
```js /useFloating/
import {
useFloating,
autoUpdate,
offset,
flip,
shift,
} from '@floating-ui/react';
function Popover() {
const [isOpen, setIsOpen] = useState(false);
const {refs, floatingStyles, context} = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
middleware: [offset(10), flip(), shift()],
whileElementsMounted: autoUpdate,
});
}
```
### 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{:.const}` that was returned from the Hook,
call the interaction Hooks:
```js /context/
import {
// ...
useClick,
useDismiss,
useRole,
useInteractions,
} from '@floating-ui/react';
function Popover() {
const [isOpen, setIsOpen] = useState(false);
const {refs, floatingStyles, context} = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
middleware: [offset(10), flip(), shift()],
whileElementsMounted: autoUpdate,
});
const click = useClick(context);
const dismiss = useDismiss(context);
const role = useRole(context);
// Merge all the interactions into prop getters
const {getReferenceProps, getFloatingProps} = useInteractions([
click,
dismiss,
role,
]);
}
```
- `useClick(){:js}` adds the ability to toggle the popover open
or closed when the reference element is clicked.
- `useDismiss(){:js}` adds the ability to dismiss the popover
when the user presses the `esc` key or presses outside of the
popover.
- `useRole(){:js}` adds the correct ARIA attributes for a
`dialog{:.string}` to the popover and reference elements.
Finally, `useInteractions(){:js}` 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.
```js
function Popover() {
// ...
return (
<>
<button ref={refs.setReference} {...getReferenceProps()}>
Reference element
</button>
{isOpen && (
<FloatingFocusManager context={context} modal={false}>
<div
ref={refs.setFloating}
style={floatingStyles}
{...getFloatingProps()}
>
Popover element
</div>
</FloatingFocusManager>
)}
</>
);
}
```
- `{...getReferenceProps()}{:js}` /
`{...getFloatingProps()}{:js}` spreads the props from the
interaction Hooks onto the relevant elements. They contain
props like `onClick{:.keyword}`, `aria-expanded{:.keyword}`,
etc.
- `<FloatingFocusManager />{:js}` 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](/docs/FloatingFocusManager).
### 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:
```js
<FloatingFocusManager context={context}>
<div />
</FloatingFocusManager>
```
#### 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{:.keyword}` prop
like so:
```js
<FloatingFocusManager context={context} modal={false}>
<div />
</FloatingFocusManager>
```
## Reusable popover component
[CodeSandbox demo](https://codesandbox.io/s/distracted-swirles-jo1pvu?file=/src/App.tsx)
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 />{:js}`
- Read the open state to change styles
```js
function App() {
return (
<Popover>
<PopoverTrigger>My trigger</PopoverTrigger>
<PopoverContent>
<PopoverHeading>My popover heading</PopoverHeading>
<PopoverDescription>
My popover description
</PopoverDescription>
<PopoverClose>Close</PopoverClose>
</PopoverContent>
</Popover>
);
}
```
### Controller component
- `<Popover />{:js}`
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(){:js}` 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 />{:js}` is the trigger button the popover is
attached to. This accepts an `asChild{:.keyword}` prop if you
want to attach it to a custom element. It also has a
`data-state{:.keyword}` attached to style based on the
open/closed state.
- `<PopoverContent />{:js}` is the popover element, which can
contain any children (React nodes).
- `<PopoverHeading />{:js}` (optional) is the heading element for
the popover.
- `<PopoverDescription />{:js}` (optional) is the description
element for the popover.
- `<PopoverClose />{:js}` (optional) is the close button for the
popover when using modal focus management.