Popover

Display popover section relative to given target element

Import

Usage

import { Popover, Text, Button } from '@mantine/core';

function Demo() {
  return (
    <Popover width={200} position="bottom" withArrow shadow="md">
      <Popover.Target>
        <Button>Toggle popover</Button>
      </Popover.Target>
      <Popover.Dropdown>
        <Text size="xs">This is uncontrolled popover, it is opened when button is clicked</Text>
      </Popover.Dropdown>
    </Popover>
  );
}

Controlled

You can control Popover state with opened and onChange props:

import { useState } from 'react';
import { Button, Popover } from '@mantine/core';

function Demo() {
  const [opened, setOpened] = useState(false);
  return (
    <Popover opened={opened} onChange={setOpened}>
      <Popover.Target>
        <Button onClick={() => setOpened((o) => !o)}>
          Toggle popover
        </Button>
      </Popover.Target>

      <Popover.Dropdown>Dropdown</Popover.Dropdown>
    </Popover>
  );
}

Controlled example with mouse events:

import { useDisclosure } from '@mantine/hooks';
import { Popover, Text, Button } from '@mantine/core';

function Demo() {
  const [opened, { close, open }] = useDisclosure(false);
  return (
    <Popover width={200} position="bottom" withArrow shadow="md" opened={opened}>
      <Popover.Target>
        <Button onMouseEnter={open} onMouseLeave={close}>
          Hover to see popover
        </Button>
      </Popover.Target>
      <Popover.Dropdown style={{ pointerEvents: 'none' }}>
        <Text size="sm">This popover is shown when user hovers the target element</Text>
      </Popover.Dropdown>
    </Popover>
  );
}

Focus trap

If you need to use interactive elements (inputs, buttons, etc.) inside Popover.Dropdown, set trapFocus prop:

import { Popover, Button, TextInput } from '@mantine/core';

function Demo() {
  return (
    <Popover width={300} trapFocus position="bottom" withArrow shadow="md">
      <Popover.Target>
        <Button>Toggle popover</Button>
      </Popover.Target>
      <Popover.Dropdown>
        <TextInput label="Name" placeholder="Name" size="xs" />
        <TextInput label="Email" placeholder="john@doe.com" size="xs" mt="xs" />
      </Popover.Dropdown>
    </Popover>
  );
}

Inline elements

Enable inline middleware to use Popover with inline elements:

Stantler’s magnificent antlers were traded at high prices as works of art. As a result, this Pokémon was hunted close to extinction by those who were after the priceless antlers. , you may catch sight of it having an intense fight with Murkrow over shiny objects.Ho-Oh’s feathers glow in seven colors depending on the angle at which they are struck by light. These feathers are said to bring happiness to the bearers. This Pokémon is said to live at the foot of a rainbow.

import { Popover, Mark, Text } from '@mantine/core';

function Demo() {
  return (
    <Text>
      Stantler’s magnificent antlers were traded at high prices as works of art. As a result, this
      Pokémon was hunted close to extinction by those who were after the priceless antlers.{' '}
      <Popover middlewares={{ flip: true, shift: true, inline: true }} position="top">
        <Popover.Target>
          <Mark>When visiting a junkyard</Mark>
        </Popover.Target>
        <Popover.Dropdown>Inline dropdown</Popover.Dropdown>
      </Popover>
      , you may catch sight of it having an intense fight with Murkrow over shiny objects.Ho-Oh’s
      feathers glow in seven colors depending on the angle at which they are struck by light. These
      feathers are said to bring happiness to the bearers. This Pokémon is said to live at the foot
      of a rainbow.
    </Text>
  );
}

Same width

Set width="target" prop to make Popover dropdown take the same width as target element:

import { Popover, Text, Button } from '@mantine/core';

function Demo() {
  return (
    <Popover width="target" position="bottom" withArrow shadow="md">
      <Popover.Target>
        <Button w={280}>Toggle popover</Button>
      </Popover.Target>
      <Popover.Dropdown>
        <Text size="sm">
          This popover has same width as target, it is useful when you are building input dropdowns
        </Text>
      </Popover.Dropdown>
    </Popover>
  );
}

offset

Set offset prop to a number to change dropdown position relative to the target element. This way you can control dropdown offset on main axis only.

Offset
import { Popover, Button, Text } from '@mantine/core';

function Demo() {
  return (
    <Popover
      width={200}
      opened
      position="bottom"
      offset={0}
    >
      <Popover.Target>
        <Button>Popover target</Button>
      </Popover.Target>
      <Popover.Dropdown>
        <Text size="xs">
          Change position and offset to configure dropdown offset relative to target
        </Text>
      </Popover.Dropdown>
    </Popover>
  );
}

To control offset on both axis, pass object with mainAxis and crossAxis properties:

Main axis
Cross axis
import { Popover, Button, Text } from '@mantine/core';

function Demo() {
  return (
    <Popover
      width={200}
      position="bottom"
      opened
      offset={{ mainAxis: 0, crossAxis: 0 }}
    >
      <Popover.Target>
        <Button>Popover target</Button>
      </Popover.Target>
      <Popover.Dropdown>
        <Text size="xs">
          Change position and offset to configure dropdown offset relative to target
        </Text>
      </Popover.Dropdown>
    </Popover>
  );
}

Middlewares

You can enable or disable Floating UI middlewares with middlewares prop:

  • shift middleware shifts the dropdown to keep it in view. It is enabled by default
  • flip middleware changes the placement of the dropdown to keep it in view. It is enabled by default.
  • inline middleware improves positioning for inline reference elements that span over multiple lines. It is disabled by default.

Example of turning off shift and flip middlewares:

import { Popover } from '@mantine/core';

function Demo() {
  return (
    <Popover
      middlewares={{ flip: false, shift: false }}
      position="bottom"
    >
      {/* Popover content */}
    </Popover>
  );
}

In this example Popover dropdown will always be below the element – it will not shift of flip if there is not enough space in the viewport.

Dropdown arrow

Set withArrow prop to add an arrow to the dropdown. Arrow is a div element rotated with transform: rotate(45deg).

arrowPosition prop determines how arrow is position relative to the target element when position is set to *-start and *-end values on Popover component. By default, the value is center – the arrow is positioned in the center of the target element if it is possible.

If you change arrowPosition to side, then the arrow will be positioned on the side of the target element, and you will be able to control arrow offset with arrowOffset prop. Note that when arrowPosition is set to center, arrowOffset prop is ignored.

Arrow position
Arrow offset
Arrow size
Arrow radius
import { Popover } from '@mantine/core';

function Demo() {
  return (
    <Popover width={200} opened position="bottom-start" withArrow>
      <Popover.Target>
        <Button>Target element</Button>
      </Popover.Target>
      <Popover.Dropdown>
        <Text size="xs">Arrow position can be changed for *-start and *-end positions</Text>
      </Popover.Dropdown>
    </Popover>
  );
}

Disabled

Set disabled prop to prevent Popover.Dropdown from rendering:

import { Popover, Text, Button } from '@mantine/core';

function Demo() {
  return (
    <Popover width={200}>
      <Popover.Target>
        <Button>Toggle popover</Button>
      </Popover.Target>
      <Popover.Dropdown>
        <Text size="xs">Disabled popover dropdown is always hidden</Text>
      </Popover.Dropdown>
    </Popover>
  );
}

Click outside

By default, Popover closes when you click outside of the dropdown. To disable this behavior, set closeOnClickOutside={false}.

You can configure events that are used for click outside detection with clickOutsideEvents prop. By default, Popover listens to mousedown and touchstart events. You can change it to any other events, for example, mouseup and touchend:

import { Popover, Text, Button } from '@mantine/core';

function Demo() {
  return (
    <Popover width={200} position="bottom" clickOutsideEvents={['mouseup', 'touchend']}>
      <Popover.Target>
        <Button>Toggle popover</Button>
      </Popover.Target>
      <Popover.Dropdown>
        <Text size="xs">Popover will be closed with mouseup and touchend events</Text>
      </Popover.Dropdown>
    </Popover>
  );
}

Initial focus

Popover uses FocusTrap component to manage focus. Add data-autofocus attribute to element that should receive initial focus:

import { Popover } from '@mantine/core';

function Demo() {
  return (
    <Popover>
      <Popover.Target>
        <button type="button">Target</button>
      </Popover.Target>
      <Popover.Dropdown>
        <input />
        <input data-autofocus />
        <input />
      </Popover.Dropdown>
    </Popover>
  );
}

Popover.Target children

Popover.Target requires an element or a component as a single child – strings, fragments, numbers and multiple elements/components are not supported and will throw error. Custom components must provide a prop to get root element ref, all Mantine components support ref out of the box.

import { Popover, Button } from '@mantine/core';

function Demo() {
  return (
    <>
      <Popover.Target>
        <button>Native button – ok</button>
      </Popover.Target>

      {/* OK */}
      <Popover.Target>
        <Button>Mantine component – ok</Button>
      </Popover.Target>

      {/* String, NOT OK – will throw error */}
      <Popover.Target>Raw string</Popover.Target>

      {/* Number, NOT OK – will throw error */}
      <Popover.Target>{2}</Popover.Target>

      {/* Fragment, NOT OK – will throw error */}
      <Popover.Target>
        <>Fragment, NOT OK, will throw error</>
      </Popover.Target>

      {/* Multiple nodes, NOT OK – will throw error */}
      <Popover.Target>
        <div>More that one node</div>
        <div>NOT OK, will throw error</div>
      </Popover.Target>
    </>
  );
}

Required ref prop

Custom components that are rendered inside Popover.Target are required to support ref prop:

// Example of code that WILL NOT WORK
import { Popover } from '@mantine/core';

function MyComponent() {
  return <div>My component</div>;
}

// This will not work – MyComponent does not support ref
function Demo() {
  return (
    <Popover>
      <Popover.Target>
        <MyComponent />
      </Popover.Target>
    </Popover>
  );
}

Use forwardRef function to forward ref to root element:

// Example of code that will work
import { forwardRef } from 'react';
import { Popover } from '@mantine/core';

const MyComponent = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>((props, ref) => (
  <div ref={ref} {...props}>
    My component
  </div>
));

// Works correctly – ref is forwarded
function Demo() {
  return (
    <Popover>
      <Popover.Target>
        <MyComponent />
      </Popover.Target>
    </Popover>
  );
}

Nested popovers

Nested popovers require children rendering without Portal. Usually, you should disable portal with props of the component that renders popover content, for example, Select has comboboxProps={{ withinPortal: false }} prop. Check documentation of the component that you are using to render popover content to find out how to disable the portal. If the portal is not disabled, outside click will close all popovers.

Example of disabling portal in Select and DatePickerInput components:

import { Button, Popover, Select } from '@mantine/core';
import { DatePickerInput } from '@mantine/dates';

function Demo() {
  return (
    <Popover width={300} position="bottom" withArrow shadow="md">
      <Popover.Target>
        <Button>Toggle popover</Button>
      </Popover.Target>
      <Popover.Dropdown bg="var(--mantine-color-body)">
        <Select
          label="Select within Popover"
          placeholder="Select within Popover"
          comboboxProps={{ withinPortal: false }}
          data={['React', 'Angular', 'Svelte', 'Vue']}
        />
        <DatePickerInput
          label="DatePickerInput within Popover"
          placeholder="DatePickerInput within Popover"
          popoverProps={{ withinPortal: false }}
          mt="md"
        />
      </Popover.Dropdown>
    </Popover>
  );
}

Accessibility

Popover follows WAI-ARIA recommendations:

  • Dropdown element has role="dialog" and aria-labelledby="target-id" attributes
  • Target element has aria-haspopup="dialog", aria-expanded, aria-controls="dropdown-id" attributes

Uncontrolled Popover will be accessible only when used with button element or component that renders it (Button, ActionIcon, etc.). Other elements will not support Space and Enter key presses.

Keyboard interactions

KeyDescriptionCondition
EscapeCloses dropdownFocus within dropdown
Space/EnterOpens/closes dropdownFocus on target element