FezVrasta/popper.js

View on GitHub
website/pages/docs/popover.mdx

Summary

Maintainability
Test Coverage
<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.