Skip to content

Popover

Popover displays additional information without interrupting user flow.

Basic Usage

To implement the Popover component, you need to import it first:

import { Popover, PopoverWrapper } from '@react-ui-org/react-ui';

And use it:

React.createElement(() => {
  const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
  // All inline styles in this example are for demonstration purposes only.
  return (
    <div
      style={{
        display: 'grid',
        placeContent: 'center',
        minWidth: '20rem',
        minHeight: '10rem',
      }}
    >
      <PopoverWrapper>
        <Button
          aria-describedby={isPopoverOpen ? 'my-popover' : undefined}
          label="Want to see a popover? Click me!"
          onClick={() => setIsPopoverOpen(!isPopoverOpen)}
        />
        {isPopoverOpen && (
          <Popover id="my-popover">
            Hello there!
          </Popover>
        )}
      </PopoverWrapper>
    </div>
  );
});

See API for all available options.

Placement

Available placements are: top, right, bottom, and left. Additionally, all basic placements can be aligned to the center (default, no suffix), start (e.g. top-start), or end (e.g. bottom-end). Check Popover API for the complete list of accepted values.

React.createElement(() => {
  const [align, setAlign] = React.useState('');
  // All inline styles in this example are for demonstration purposes only.
  return (
    <>
      <Toolbar align="baseline">
        <ToolbarItem>
          <span id="alignment-options-label">Alignment:</span>
        </ToolbarItem>
        <ToolbarItem>
          <ButtonGroup aria-labelledby="alignment-options-label" priority="outline">
            <Button
              aria-pressed={align === '-start'}
              color={align === '-start' ? 'selected' : 'secondary'}
              label="start"
              onClick={() => setAlign('-start')}
            />
            <Button
              aria-pressed={align === ''}
              color={align === '' ? 'selected' : 'secondary'}
              label="center"
              onClick={() => setAlign('')}
            />
            <Button
              aria-pressed={align === '-end'}
              color={align === '-end' ? 'selected' : 'secondary'}
              label="end"
              onClick={() => setAlign('-end')}
            />
          </ButtonGroup>
        </ToolbarItem>
      </Toolbar>
      <div
        style={{
          display: 'grid',
          placeContent: 'center',
          minWidth: '20rem',
          minHeight: '15rem',
        }}
      >
        <PopoverWrapper>
          <docoff-placeholder bordered aria-describedby="my-popover-top">
            Popovers
            <br />
            all day long…
          </docoff-placeholder>
          <Popover id="my-popover-top" placement={`top${align}`}>
            Top side
          </Popover>
          <Popover id="my-popover-right" placement={`right${align}`}>
            Right side
          </Popover>
          <Popover id="my-popover-bottom" placement={`bottom${align}`}>
            Bottom side
          </Popover>
          <Popover id="my-popover-left" placement={`left${align}`}>
            Left side
          </Popover>
        </PopoverWrapper>
      </div>
    </>
  );
});

PopoverWrapper

PopoverWrapper is an optional wrapper to make positioning of Popover even easier.

By default, Popover is placed relative to the closest parent element with position: relative or position: absolute. Maybe you already have one of these in your CSS. PopoverWrapper is here for situations when you don't.

<PopoverWrapper>
  <Button
    aria-describedby={isPopoverOpen ? 'my-popover' : undefined}
    label="Want to see a popover? Click me!"
    onClick={() => setIsPopoverOpen(!isPopoverOpen)}
  />
  {isPopoverOpen && <Popover id="my-popover">Hello there!</Popover>}
</PopoverWrapper>

How do you know you may need PopoverWrapper?

  • You are not rendering Popover in a React portal.
  • You are using Popover in a complex layout and it does not pop up where you need it.
  • You are using Floating UI with absolute positioning strategy (see Advanced Positioning below) and your Popover keeps to be misplaced.
  • You have no idea what CSS position is and just want to get it working.

To sum it up, usually you will need either PopoverWrapper around your content or position: [ relative | absolute ] somewhere in your CSS (but you never need both!). Nevertheless, in the simplest situations, like in a single-column page layout, you may not need either of these at all.

Head to PopoverWrapper API for all available options.

Advanced Positioning

While the basic setup can be sufficient in some scenarios, dropping a Popover usually won't be so easy. To handle all tricky situations and edge cases automatically, including smart position updates to ensure Popover visibility, we recommend to involve an external library designed specifically for this purpose.

ℹ️ The following example is using external library Floating UI. To use Floating UI, install it first:

npm install --save @floating-ui/react-dom

And import it along with Popover, e.g.:

import FloatingUIReactDOM from '@floating-ui/react-dom';
import { Popover } from '@react-ui-org/react-ui';

As opposed to the basic setup, Popover will be placed according to its triggering component (reference), but still recognizing the closest parent element with position: relative or position: absolute if there is any.

Popover reacts on the ref option, necessary for advanced positioning: when ref is set, Popover resets its built-in positioning and relies on provided style.

👉 Please consult Floating UI documentation to understand how it works and to get an idea of all possible cases you may need to cover.

🖱 Try scrolling the example to see how Popover placement is updated.

React.createElement(() => {
  const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
  const [placement, setPlacement] = React.useState('top');
  const {
    x,
    y,
    reference,
    floating,
    placement: finalPlacement,
    strategy,
  } = FloatingUIReactDOM.useFloating({
    placement,
    middleware: [FloatingUIReactDOM.flip()],
    whileElementsMounted: FloatingUIReactDOM.autoUpdate,
  });
  const placementOptions = [
    'top',
    'top-start',
    'top-end',
    'right',
    'right-start',
    'right-end',
    'bottom',
    'bottom-start',
    'bottom-end',
    'left',
    'left-start',
    'left-end',
  ];
  // All inline styles in this example EXCEPT Popover `style` are for
  // demonstration purposes only.
  return (
    <>
      <Toolbar>
        <ToolbarItem>
          <SelectField
            label="Suggested placement:"
            onChange={e => setPlacement(e.target.value)}
            options={placementOptions.map((el) => ({
              label: el,
              value: el,
            }))}
            value={placement}
          />
        </ToolbarItem>
        <ToolbarItem>
          <div className="mb-2">Final placement:</div>
          <code>{finalPlacement}</code>
        </ToolbarItem>
      </Toolbar>
      <div
        style={{
          width: '40rem',
          maxWidth: '100%',
          height: '10rem',
          overflow: 'auto',
        }}
      >
        <div
          style={{
            position: 'relative',
            width: '60rem',
            height: '20rem',
            paddingBlock: '7rem',
            textAlign: 'center',
          }}
        >
          <Button
            aria-describedby={isPopoverOpen ? 'my-advanced-popover' : undefined}
            label="Trigger Popover"
            onClick={() => setIsPopoverOpen(!isPopoverOpen)}
            ref={reference}
          />
          {isPopoverOpen && (
            <Popover
              id="my-advanced-popover"
              placement={finalPlacement}
              style={{
                position: strategy,
                top: y ? y : '',
                left: x ? x : '',
              }}
              ref={floating}
            >
              Auto-flipping Popover
            </Popover>
          )}
        </div>
      </div>
    </>
  );
});

Forwarding HTML Attributes

In addition to the options below in the component's API section, you can specify React synthetic events or any HTML attribute you like. All attributes that don't interfere with the API are forwarded to the root <div> HTML element. This enables making the component interactive and helps to improve its accessibility.

👉 Refer to the MDN reference for the full list of supported attributes of the div element.

Forwarding ref

If you provide ref, it is forwarded to the root native HTML <div> element, which enables Advanced Positioning.

API

PopoverWrapper API

Theming

Custom Property Description
--rui-Popover__width Popover width
--rui-Popover__padding Popover padding
--rui-Popover__border-width Border width
--rui-Popover__border-color Border color
--rui-Popover__border-radius Corner radius
--rui-Popover__color Text color
--rui-Popover__background-color Background color
--rui-Popover__box-shadow Popover box shadow