Popover

A popover is an overlay element positioned relative to a trigger.

installyarn add react-aria-components
version1.1.1
usageimport {Popover} from 'react-aria-components'

Example#


import {Button, Dialog, DialogTrigger, Heading, OverlayArrow, Popover, Switch} from 'react-aria-components';

<DialogTrigger>
  <Button>Settings</Button>
  <Popover>
    <OverlayArrow>
      <svg width={12} height={12} viewBox="0 0 12 12">
        <path d="M0 0 L6 6 L12 0" />
      </svg>
    </OverlayArrow>
    <Dialog>
      <div className="flex-col">
        <Switch defaultSelected>
          <div className="indicator" /> Wi-Fi
        </Switch>
        <Switch defaultSelected>
          <div className="indicator" /> Bluetooth
        </Switch>
        <Switch>
          <div className="indicator" /> Mute
        </Switch>
      </div>
    </Dialog>
  </Popover>
</DialogTrigger>
import {
  Button,
  Dialog,
  DialogTrigger,
  Heading,
  OverlayArrow,
  Popover,
  Switch
} from 'react-aria-components';

<DialogTrigger>
  <Button>Settings</Button>
  <Popover>
    <OverlayArrow>
      <svg width={12} height={12} viewBox="0 0 12 12">
        <path d="M0 0 L6 6 L12 0" />
      </svg>
    </OverlayArrow>
    <Dialog>
      <div className="flex-col">
        <Switch defaultSelected>
          <div className="indicator" /> Wi-Fi
        </Switch>
        <Switch defaultSelected>
          <div className="indicator" /> Bluetooth
        </Switch>
        <Switch>
          <div className="indicator" /> Mute
        </Switch>
      </div>
    </Dialog>
  </Popover>
</DialogTrigger>
import {
  Button,
  Dialog,
  DialogTrigger,
  Heading,
  OverlayArrow,
  Popover,
  Switch
} from 'react-aria-components';

<DialogTrigger>
  <Button>
    Settings
  </Button>
  <Popover>
    <OverlayArrow>
      <svg
        width={12}
        height={12}
        viewBox="0 0 12 12"
      >
        <path d="M0 0 L6 6 L12 0" />
      </svg>
    </OverlayArrow>
    <Dialog>
      <div className="flex-col">
        <Switch
          defaultSelected
        >
          <div className="indicator" />
          {' '}
          Wi-Fi
        </Switch>
        <Switch
          defaultSelected
        >
          <div className="indicator" />
          {' '}
          Bluetooth
        </Switch>
        <Switch>
          <div className="indicator" />
          {' '}
          Mute
        </Switch>
      </div>
    </Dialog>
  </Popover>
</DialogTrigger>
Show CSS
@import "@react-aria/example-theme";

.react-aria-Popover {
  --background-color: var(--overlay-background);

  border: 1px solid var(--border-color);
  box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
  border-radius: 6px;
  background: var(--background-color);
  color: var(--text-color);
  outline: none;
  max-width: 250px;
  box-sizing: border-box;

  .react-aria-OverlayArrow svg {
    display: block;
    fill: var(--background-color);
    stroke: var(--border-color);
    stroke-width: 1px;
  }

  &[data-placement=top] {
    --origin: translateY(8px);

    &:has(.react-aria-OverlayArrow) {
      margin-bottom: 6px;
    }
  }

  &[data-placement=bottom] {
    --origin: translateY(-8px);

    &:has(.react-aria-OverlayArrow) {
      margin-top: 6px;
    }

    .react-aria-OverlayArrow svg {
      transform: rotate(180deg);
    }
  }

  &[data-placement=right] {
    --origin: translateX(-8px);

    &:has(.react-aria-OverlayArrow) {
      margin-left: 6px;
    }

    .react-aria-OverlayArrow svg {
      transform: rotate(90deg);
    }
  }

  &[data-placement=left] {
    --origin: translateX(8px);

    &:has(.react-aria-OverlayArrow) {
      margin-right: 6px;
    }

    .react-aria-OverlayArrow svg {
      transform: rotate(-90deg);
    }
  }

  &[data-entering] {
    animation: popover-slide 200ms;
  }

  &[data-exiting] {
    animation: popover-slide 200ms reverse ease-in;
  }
}

@keyframes popover-slide {
  from {
    transform: var(--origin);
    opacity: 0;
  }

  to {
    transform: translateY(0);
    opacity: 1;
  }
}
@import "@react-aria/example-theme";

.react-aria-Popover {
  --background-color: var(--overlay-background);

  border: 1px solid var(--border-color);
  box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
  border-radius: 6px;
  background: var(--background-color);
  color: var(--text-color);
  outline: none;
  max-width: 250px;
  box-sizing: border-box;

  .react-aria-OverlayArrow svg {
    display: block;
    fill: var(--background-color);
    stroke: var(--border-color);
    stroke-width: 1px;
  }

  &[data-placement=top] {
    --origin: translateY(8px);

    &:has(.react-aria-OverlayArrow) {
      margin-bottom: 6px;
    }
  }

  &[data-placement=bottom] {
    --origin: translateY(-8px);

    &:has(.react-aria-OverlayArrow) {
      margin-top: 6px;
    }

    .react-aria-OverlayArrow svg {
      transform: rotate(180deg);
    }
  }

  &[data-placement=right] {
    --origin: translateX(-8px);

    &:has(.react-aria-OverlayArrow) {
      margin-left: 6px;
    }

    .react-aria-OverlayArrow svg {
      transform: rotate(90deg);
    }
  }

  &[data-placement=left] {
    --origin: translateX(8px);

    &:has(.react-aria-OverlayArrow) {
      margin-right: 6px;
    }

    .react-aria-OverlayArrow svg {
      transform: rotate(-90deg);
    }
  }

  &[data-entering] {
    animation: popover-slide 200ms;
  }

  &[data-exiting] {
    animation: popover-slide 200ms reverse ease-in;
  }
}

@keyframes popover-slide {
  from {
    transform: var(--origin);
    opacity: 0;
  }

  to {
    transform: translateY(0);
    opacity: 1;
  }
}
@import "@react-aria/example-theme";

.react-aria-Popover {
  --background-color: var(--overlay-background);

  border: 1px solid var(--border-color);
  box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
  border-radius: 6px;
  background: var(--background-color);
  color: var(--text-color);
  outline: none;
  max-width: 250px;
  box-sizing: border-box;

  .react-aria-OverlayArrow svg {
    display: block;
    fill: var(--background-color);
    stroke: var(--border-color);
    stroke-width: 1px;
  }

  &[data-placement=top] {
    --origin: translateY(8px);

    &:has(.react-aria-OverlayArrow) {
      margin-bottom: 6px;
    }
  }

  &[data-placement=bottom] {
    --origin: translateY(-8px);

    &:has(.react-aria-OverlayArrow) {
      margin-top: 6px;
    }

    .react-aria-OverlayArrow svg {
      transform: rotate(180deg);
    }
  }

  &[data-placement=right] {
    --origin: translateX(-8px);

    &:has(.react-aria-OverlayArrow) {
      margin-left: 6px;
    }

    .react-aria-OverlayArrow svg {
      transform: rotate(90deg);
    }
  }

  &[data-placement=left] {
    --origin: translateX(8px);

    &:has(.react-aria-OverlayArrow) {
      margin-right: 6px;
    }

    .react-aria-OverlayArrow svg {
      transform: rotate(-90deg);
    }
  }

  &[data-entering] {
    animation: popover-slide 200ms;
  }

  &[data-exiting] {
    animation: popover-slide 200ms reverse ease-in;
  }
}

@keyframes popover-slide {
  from {
    transform: var(--origin);
    opacity: 0;
  }

  to {
    transform: translateY(0);
    opacity: 1;
  }
}

Features#


There is no built in way to create popovers in HTML. Popover helps achieve accessible popovers that can be styled as needed.

  • Styleable – States for entry and exit animations are included for easy styling, and an optional arrow element can be rendered.
  • Accessible – The trigger and popover are automatically associated semantically via ARIA. Content outside the popover is hidden from assistive technologies while it is open. The popover closes when interacting outside, or pressing the Escape key.
  • Focus management – Focus is moved into the popover on mount, and restored to the trigger element on unmount.
  • Positioning – The popover is positioned relative to the trigger element, and automatically flips and adjusts to avoid overlapping with the edge of the browser window.

Note: Popover only provides the overlay itself. It should be combined with Dialog to create fully accessible popovers. Other overlays such as menus may also be placed in a popover.

Anatomy#


Arrow (optional)Popover

A popover consists of a trigger element (e.g. button) and an overlay, which is positioned relative to the trigger. The overlay may contain a Dialog, or another element such as a Menu or ListBox when used within a component such as a Select or ComboBox.

import {Button, Dialog, DialogTrigger, OverlayArrow, Popover} from 'react-aria-components';

<DialogTrigger>
  <Button />
  <Popover>
    <OverlayArrow />
    <Dialog />
  </Popover>
</DialogTrigger>
import {
  Button,
  Dialog,
  DialogTrigger,
  OverlayArrow,
  Popover
} from 'react-aria-components';

<DialogTrigger>
  <Button />
  <Popover>
    <OverlayArrow />
    <Dialog />
  </Popover>
</DialogTrigger>
import {
  Button,
  Dialog,
  DialogTrigger,
  OverlayArrow,
  Popover
} from 'react-aria-components';

<DialogTrigger>
  <Button />
  <Popover>
    <OverlayArrow />
    <Dialog />
  </Popover>
</DialogTrigger>

Examples#


Notifications Popover
A notifications popover styled with Tailwind CSS.

Starter kits#


To help kick-start your project, we offer starter kits that include example implementations of all React Aria components with various styling solutions. All components are fully styled, including support for dark mode, high contrast mode, and all UI states. Each starter comes with a pre-configured Storybook that you can experiment with, or use as a starting point for your own component library.

Vanilla CSS
Download ZIP
Preview
Tailwind CSS
Download ZIP
Preview

Reusable wrappers#


If you will use a Popover in multiple places in your app, you can wrap all of the pieces into a reusable component. This way, the DOM structure, styling code, and other logic are defined in a single place and reused everywhere to ensure consistency.

This example wraps Popover and all of its children together into a single component. Since the Dialog is built in, this means it can't be used for components like Select, Menu, and ComboBox. Exclude the dialog if your popover will be reused in these components.

import type {PopoverProps} from 'react-aria-components';

interface MyPopoverProps extends Omit<PopoverProps, 'children'> {
  children: React.ReactNode;
}

function MyPopover({ children, ...props }: MyPopoverProps) {
  return (
    <Popover {...props}>
      <OverlayArrow>
        <svg width={12} height={12} viewBox="0 0 12 12">
          <path d="M0 0 L6 6 L12 0" />
        </svg>
      </OverlayArrow>
      <Dialog>
        {children}
      </Dialog>
    </Popover>
  );
}

<DialogTrigger>
  <Button aria-label="Help"></Button>
  <MyPopover>
    <Heading slot="title">Help</Heading>
    <p>For help accessing your account, please contact support.</p>
  </MyPopover>
</DialogTrigger>
import type {PopoverProps} from 'react-aria-components';

interface MyPopoverProps
  extends Omit<PopoverProps, 'children'> {
  children: React.ReactNode;
}

function MyPopover({ children, ...props }: MyPopoverProps) {
  return (
    <Popover {...props}>
      <OverlayArrow>
        <svg width={12} height={12} viewBox="0 0 12 12">
          <path d="M0 0 L6 6 L12 0" />
        </svg>
      </OverlayArrow>
      <Dialog>
        {children}
      </Dialog>
    </Popover>
  );
}

<DialogTrigger>
  <Button aria-label="Help"></Button>
  <MyPopover>
    <Heading slot="title">Help</Heading>
    <p>
      For help accessing your account, please contact
      support.
    </p>
  </MyPopover>
</DialogTrigger>
import type {PopoverProps} from 'react-aria-components';

interface MyPopoverProps
  extends
    Omit<
      PopoverProps,
      'children'
    > {
  children:
    React.ReactNode;
}

function MyPopover(
  { children, ...props }:
    MyPopoverProps
) {
  return (
    <Popover {...props}>
      <OverlayArrow>
        <svg
          width={12}
          height={12}
          viewBox="0 0 12 12"
        >
          <path d="M0 0 L6 6 L12 0" />
        </svg>
      </OverlayArrow>
      <Dialog>
        {children}
      </Dialog>
    </Popover>
  );
}

<DialogTrigger>
  <Button aria-label="Help"></Button>
  <MyPopover>
    <Heading slot="title">
      Help
    </Heading>
    <p>
      For help
      accessing your
      account, please
      contact support.
    </p>
  </MyPopover>
</DialogTrigger>

Positioning#


Placement#

The popover's placement with respect to its anchor element can be adjusted using the placement prop. See Placement for a full list of available placement combinations. Popovers will also automatically flip to the opposite direction if there isn't enough space.

<div style={{ display: 'flex', gap: 8 }}>
  <DialogTrigger>
    <Button>⬅️</Button>
    <MyPopover placement="start">
      In left-to-right, this is on the left. In right-to-left, this is on the
      right.
    </MyPopover>
  </DialogTrigger>
  <DialogTrigger>
    <Button>⬆️</Button>
    <MyPopover placement="top">This popover is above the button.</MyPopover>
  </DialogTrigger>
  <DialogTrigger>
    <Button>⬇️</Button>
    <MyPopover placement="bottom">
      This popover is below the button.
    </MyPopover>
  </DialogTrigger>
  <DialogTrigger>
    <Button>➡️</Button>
    <MyPopover placement="end">
      In left-to-right, this is on the right. In right-to-left, this is on the
      left.
    </MyPopover>
  </DialogTrigger>
</div>
<div style={{ display: 'flex', gap: 8 }}>
  <DialogTrigger>
    <Button>⬅️</Button>
    <MyPopover placement="start">
      In left-to-right, this is on the left. In
      right-to-left, this is on the right.
    </MyPopover>
  </DialogTrigger>
  <DialogTrigger>
    <Button>⬆️</Button>
    <MyPopover placement="top">
      This popover is above the button.
    </MyPopover>
  </DialogTrigger>
  <DialogTrigger>
    <Button>⬇️</Button>
    <MyPopover placement="bottom">
      This popover is below the button.
    </MyPopover>
  </DialogTrigger>
  <DialogTrigger>
    <Button>➡️</Button>
    <MyPopover placement="end">
      In left-to-right, this is on the right. In
      right-to-left, this is on the left.
    </MyPopover>
  </DialogTrigger>
</div>
<div
  style={{
    display: 'flex',
    gap: 8
  }}
>
  <DialogTrigger>
    <Button>⬅️</Button>
    <MyPopover placement="start">
      In left-to-right,
      this is on the
      left. In
      right-to-left,
      this is on the
      right.
    </MyPopover>
  </DialogTrigger>
  <DialogTrigger>
    <Button>⬆️</Button>
    <MyPopover placement="top">
      This popover is
      above the button.
    </MyPopover>
  </DialogTrigger>
  <DialogTrigger>
    <Button>⬇️</Button>
    <MyPopover placement="bottom">
      This popover is
      below the button.
    </MyPopover>
  </DialogTrigger>
  <DialogTrigger>
    <Button>➡️</Button>
    <MyPopover placement="end">
      In left-to-right,
      this is on the
      right. In
      right-to-left,
      this is on the
      left.
    </MyPopover>
  </DialogTrigger>
</div>

Offset and cross offset#

The popover's offset with respect to its anchor element can be adjusted using the offset and crossOffset props. The offset prop controls the spacing applied along the main axis between the element and its anchor element whereas the crossOffset prop handles the spacing applied along the cross axis.

Below is a popover offset by an additional 50px above the trigger.

<DialogTrigger>
  <Button>Offset</Button>
  <MyPopover placement="top" offset={50}>
    Offset by an additional 50px.
  </MyPopover>
</DialogTrigger>
<DialogTrigger>
  <Button>Offset</Button>
  <MyPopover placement="top" offset={50}>
    Offset by an additional 50px.
  </MyPopover>
</DialogTrigger>
<DialogTrigger>
  <Button>
    Offset
  </Button>
  <MyPopover
    placement="top"
    offset={50}
  >
    Offset by an
    additional 50px.
  </MyPopover>
</DialogTrigger>

Below is a popover cross offset by an additional 100px to the right of the trigger.

<DialogTrigger>
  <Button>Cross offset</Button>
  <MyPopover placement="top" crossOffset={100}>
    Offset by an additional 100px.
  </MyPopover>
</DialogTrigger>
<DialogTrigger>
  <Button>Cross offset</Button>
  <MyPopover placement="top" crossOffset={100}>
    Offset by an additional 100px.
  </MyPopover>
</DialogTrigger>
<DialogTrigger>
  <Button>
    Cross offset
  </Button>
  <MyPopover
    placement="top"
    crossOffset={100}
  >
    Offset by an
    additional 100px.
  </MyPopover>
</DialogTrigger>

Flipping#

By default, usePopover attempts to flip popovers on the main axis in situations where the original placement would cause it to render out of view. This can be overridden by setting shouldFlip={false}. To see the difference between the two options, scroll this page so that the example below is near the bottom of the window.

<div className="flex-row">
  <DialogTrigger>
    <Button>Default</Button>
    <MyPopover placement="bottom">
      This is a popover that will flip if it can't fully render below the
      button.
    </MyPopover>
  </DialogTrigger>
   <DialogTrigger>
    <Button>shouldFlip=false</Button>
    <MyPopover placement="bottom" shouldFlip={false}>
      This is a popover that won't flip if it can't fully render below the
      button.
    </MyPopover>
  </DialogTrigger>
</div>
<div className="flex-row">
  <DialogTrigger>
    <Button>Default</Button>
    <MyPopover placement="bottom">
      This is a popover that will flip if it can't fully
      render below the button.
    </MyPopover>
  </DialogTrigger>
   <DialogTrigger>
    <Button>shouldFlip=false</Button>
    <MyPopover placement="bottom" shouldFlip={false}>
      This is a popover that won't flip if it can't fully
      render below the button.
    </MyPopover>
  </DialogTrigger>
</div>
<div className="flex-row">
  <DialogTrigger>
    <Button>
      Default
    </Button>
    <MyPopover placement="bottom">
      This is a popover
      that will flip if
      it can't fully
      render below the
      button.
    </MyPopover>
  </DialogTrigger>
   <DialogTrigger>
    <Button>
      shouldFlip=false
    </Button>
    <MyPopover
      placement="bottom"
      shouldFlip={false}
    >
      This is a popover
      that won't flip
      if it can't fully
      render below the
      button.
    </MyPopover>
  </DialogTrigger>
</div>

Container padding#

You can control the minimum padding required between the popover and the surrounding container via the containerPadding prop. This affects the positioning breakpoints that determine when it will attempt to flip.

The example below will maintain at least 50px between the popover and the edge of the browser window.

<DialogTrigger>
  <Button>Container padding</Button>
  <MyPopover placement="top" containerPadding={50}>
    This is a popover.
  </MyPopover>
</DialogTrigger>
<DialogTrigger>
  <Button>Container padding</Button>
  <MyPopover placement="top" containerPadding={50}>
    This is a popover.
  </MyPopover>
</DialogTrigger>
<DialogTrigger>
  <Button>
    Container padding
  </Button>
  <MyPopover
    placement="top"
    containerPadding={50}
  >
    This is a popover.
  </MyPopover>
</DialogTrigger>

Controlled open state#


The above examples have shown Popover used within a <DialogTrigger>, which handles opening the popover when a button is clicked, and positioning relative to the trigger. This is convenient, but there are cases where you want to show a popover programmatically rather than as a result of a user action, or render the <Popover> in a different part of the JSX tree.

To do this, you can manage the popover's isOpen state yourself and provide it as a prop to the <Popover> element. The onOpenChange prop will be called when the user closes the popover, and should be used to update your state. In addition, the triggerRef prop must be set to the element that the popover should be positioned relative to.

function Example() {
  let [isOpen, setOpen] = React.useState(false);
  let triggerRef = React.useRef(null);

  return (
    <>
      <Button onPress={() => setOpen(true)}>Trigger</Button>
      <span ref={triggerRef} style={{ paddingLeft: 12 }}>
        Popover will be positioned relative to me
      </span>
      <MyPopover triggerRef={triggerRef} isOpen={isOpen} onOpenChange={setOpen}>
        <Heading slot="title">Popover</Heading>
        <div>I'm over here!</div>
      </MyPopover>
    </>
  );
}
function Example() {
  let [isOpen, setOpen] = React.useState(false);
  let triggerRef = React.useRef(null);

  return (
    <>
      <Button onPress={() => setOpen(true)}>Trigger</Button>
      <span ref={triggerRef} style={{ paddingLeft: 12 }}>
        Popover will be positioned relative to me
      </span>
      <MyPopover
        triggerRef={triggerRef}
        isOpen={isOpen}
        onOpenChange={setOpen}
      >
        <Heading slot="title">Popover</Heading>
        <div>I'm over here!</div>
      </MyPopover>
    </>
  );
}
function Example() {
  let [isOpen, setOpen] =
    React.useState(
      false
    );
  let triggerRef = React
    .useRef(null);

  return (
    <>
      <Button
        onPress={() =>
          setOpen(true)}
      >
        Trigger
      </Button>
      <span
        ref={triggerRef}
        style={{
          paddingLeft: 12
        }}
      >
        Popover will be
        positioned
        relative to me
      </span>
      <MyPopover
        triggerRef={triggerRef}
        isOpen={isOpen}
        onOpenChange={setOpen}
      >
        <Heading slot="title">
          Popover
        </Heading>
        <div>
          I'm over here!
        </div>
      </MyPopover>
    </>
  );
}

Props#


Popover#

NameTypeDefaultDescription
triggerstring

The name of the component that triggered the popover. This is reflected on the element as the data-trigger attribute, and can be used to provide specific styles for the popover depending on which element triggered it.

triggerRefRefObject<Element>

The ref for the element which the popover positions itself with respect to.

When used within a trigger component such as DialogTrigger, MenuTrigger, Select, etc., this is set automatically. It is only required when used standalone.

isEnteringbooleanWhether the popover is currently performing an entry animation.
isExitingbooleanWhether the popover is currently performing an exit animation.
UNSTABLE_portalContainerElementdocument.bodyThe container element in which the overlay portal will be placed. This may have unknown behavior depending on where it is portalled to.
offsetnumber8

The additional offset applied along the main axis between the element and its anchor element.

placementPlacement'bottom'The placement of the element with respect to its anchor element.
containerPaddingnumber12

The placement padding that should be applied between the element and its surrounding container.

crossOffsetnumber0

The additional offset applied along the cross axis between the element and its anchor element.

shouldFlipbooleantrue

Whether the element should flip its orientation (e.g. top to bottom or left to right) when there is insufficient room for it to render completely.

isNonModalboolean

Whether the popover is non-modal, i.e. elements outside the popover may be interacted with by assistive technologies.

Most popovers should not use this option as it may negatively impact the screen reader experience. Only use with components such as combobox, which are designed to handle this situation carefully.

isKeyboardDismissDisabledbooleanfalse

Whether pressing the escape key to close the popover should be disabled.

Most popovers should not use this option. When set to true, an alternative way to close the popover with a keyboard must be provided.

shouldCloseOnInteractOutside( (element: Element )) => boolean

When user interacts with the argument element outside of the popover ref, return true if onClose should be called. This gives you a chance to filter out interaction with elements that should not dismiss the popover. By default, onClose will always be called on interaction outside the popover ref.

boundaryElementElementdocument.bodyElement that that serves as the positioning boundary.
scrollRefRefObject<Element>overlayRefA ref for the scrollable region within the overlay.
shouldUpdatePositionbooleantrueWhether the overlay should update its position automatically.
arrowBoundaryOffsetnumber0The minimum distance the arrow's edge should be from the edge of the overlay element.
isOpenbooleanWhether the overlay is open by default (controlled).
defaultOpenbooleanWhether the overlay is open by default (uncontrolled).
childrenReactNode( (values: PopoverRenderProps )) => ReactNodeThe children of the component. A function may be provided to alter the children based on component state.
classNamestring( (values: PopoverRenderProps )) => stringThe CSS className for the element. A function may be provided to compute the class based on component state.
styleCSSProperties( (values: PopoverRenderProps )) => CSSPropertiesThe inline style for the element. A function may be provided to compute the style based on component state.
Events
NameTypeDescription
onOpenChange( (isOpen: boolean )) => voidHandler that is called when the overlay's open state changes.
Layout
NameTypeDescription
slotstringnull

A slot name for the component. Slots allow the component to receive props from a parent component. An explicit null value indicates that the local props completely override all props received from a parent.

Sizing
NameTypeDescription
maxHeightnumber

The maxHeight specified for the overlay element. By default, it will take all space up to the current viewport height.

OverlayArrow#

OverlayArrow accepts all HTML attributes.

Styling#


React Aria components can be styled in many ways, including using CSS classes, inline styles, utility classes (e.g. Tailwind), CSS-in-JS (e.g. Styled Components), etc. By default, all components include a builtin className attribute which can be targeted using CSS selectors. These follow the react-aria-ComponentName naming convention.

.react-aria-Popover {
  /* ... */
}
.react-aria-Popover {
  /* ... */
}
.react-aria-Popover {
  /* ... */
}

A custom className can also be specified on any component. This overrides the default className provided by React Aria with your own.

<Popover className="my-popover">
  {/* ... */}
</Popover>
<Popover className="my-popover">
  {/* ... */}
</Popover>
<Popover className="my-popover">
  {/* ... */}
</Popover>

In addition, some components support multiple UI states (e.g. focused, placeholder, readonly, etc.). React Aria components expose states using data attributes, which you can target in CSS selectors. For example:

.react-aria-Popover[data-placement=left] {
  /* ... */
}
.react-aria-Popover[data-placement=left] {
  /* ... */
}
.react-aria-Popover[data-placement=left] {
  /* ... */
}

The className and style props also accept functions which receive states for styling. This lets you dynamically determine the classes or styles to apply, which is useful when using utility CSS libraries like Tailwind.

<OverlayArrow
  className={({ placement }) =>
    placement === 'left' || placement === 'right' ? 'rotate-90' : 'rotate-0'}
>
  {/* ... */}
</OverlayArrow>
<OverlayArrow
  className={({ placement }) =>
    placement === 'left' || placement === 'right'
      ? 'rotate-90'
      : 'rotate-0'}
>
  {/* ... */}
</OverlayArrow>
<OverlayArrow
  className={(
    { placement }
  ) =>
    placement ===
        'left' ||
      placement ===
        'right'
      ? 'rotate-90'
      : 'rotate-0'}
>
  {/* ... */}
</OverlayArrow>

Popovers also support entry and exit animations via states exposed as data attributes and render props. Popover will automatically wait for any exit animations to complete before it is removed from the DOM.

.react-aria-Popover[data-entering] {
  animation: slide 300ms;
}

.react-aria-Popover[data-exiting] {
  animation: slide 300ms reverse;
}

@keyframes slide {
  /* ... */
}
.react-aria-Popover[data-entering] {
  animation: slide 300ms;
}

.react-aria-Popover[data-exiting] {
  animation: slide 300ms reverse;
}

@keyframes slide {
  /* ... */
}
.react-aria-Popover[data-entering] {
  animation: slide 300ms;
}

.react-aria-Popover[data-exiting] {
  animation: slide 300ms reverse;
}

@keyframes slide {
  /* ... */
}

The states, selectors, and render props for each component used in a Popover are documented below.

Popover#

A Popover can be targeted with the .react-aria-Popover CSS selector, or by overriding with a custom className. It supports the following states and render props:

NameCSS SelectorDescription
trigger[data-trigger="..."]The name of the component that triggered the popover, e.g. "DialogTrigger" or "ComboBox".
placement[data-placement="left | right | top | bottom"]The placement of the popover relative to the trigger.
isEntering[data-entering]Whether the popover is currently entering. Use this to apply animations.
isExiting[data-exiting]Whether the popover is currently exiting. Use this to apply animations.

OverlayArrow#

A OverlayArrow can be targeted with the .react-aria-OverlayArrow CSS selector, or by overriding with a custom className. It supports the following states and render props:

NameCSS SelectorDescription
placement[data-placement="left | right | top | bottom"]The placement of the overlay relative to the trigger.

Advanced customization#


Contexts#

All React Aria Components export a corresponding context that can be used to send props to them from a parent element. This enables you to build your own compositional APIs similar to those found in React Aria Components itself. You can send any prop or ref via context that you could pass to the corresponding component. The local props and ref on the component are merged with the ones passed via context, with the local props taking precedence (following the rules documented in mergeProps).

ComponentContextPropsRef
PopoverPopoverContextPopoverPropsHTMLElement

This example shows a HelpTrigger component that wraps a button and a popover and shows the popover when the user presses the ? key. It uses ButtonContext to provide a keyboard listener and ref to a nested button, and PopoverContext to provide the trigger ref and open state to the nested popover.

import {ButtonContext, PopoverContext} from 'react-aria-components';

interface HelpTriggerProps {
  children?: React.ReactNode;
}

function HelpTrigger({ children }: HelpTriggerProps) {
  let triggerRef = React.useRef(null);
  let [isOpen, setOpen] = React.useState(false);
  let onKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === '?') {
      setOpen(true);
    }
  };

  return (
    <ButtonContext.Provider value={{ onKeyDown, ref: triggerRef }}>
      <PopoverContext.Provider
        value={{ triggerRef, isOpen, onOpenChange: setOpen }}
      >        {children}
      </PopoverContext.Provider>
    </ButtonContext.Provider>
  );
}

<HelpTrigger>
  <Button>Press ? for help</Button>
  <MyPopover>
    <Heading slot="title">Help</Heading>
    <div>Do you need help?</div>
  </MyPopover>
</HelpTrigger>
import {
  ButtonContext,
  PopoverContext
} from 'react-aria-components';

interface HelpTriggerProps {
  children?: React.ReactNode;
}

function HelpTrigger({ children }: HelpTriggerProps) {
  let triggerRef = React.useRef(null);
  let [isOpen, setOpen] = React.useState(false);
  let onKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === '?') {
      setOpen(true);
    }
  };

  return (
    <ButtonContext.Provider
      value={{ onKeyDown, ref: triggerRef }}
    >
      <PopoverContext.Provider
        value={{
          triggerRef,
          isOpen,
          onOpenChange: setOpen
        }}
      >        {children}
      </PopoverContext.Provider>
    </ButtonContext.Provider>
  );
}

<HelpTrigger>
  <Button>Press ? for help</Button>
  <MyPopover>
    <Heading slot="title">Help</Heading>
    <div>Do you need help?</div>
  </MyPopover>
</HelpTrigger>
import {
  ButtonContext,
  PopoverContext
} from 'react-aria-components';

interface HelpTriggerProps {
  children?:
    React.ReactNode;
}

function HelpTrigger(
  { children }:
    HelpTriggerProps
) {
  let triggerRef = React
    .useRef(null);
  let [isOpen, setOpen] =
    React.useState(
      false
    );
  let onKeyDown = (
    e: React.KeyboardEvent
  ) => {
    if (e.key === '?') {
      setOpen(true);
    }
  };

  return (
    <ButtonContext.Provider
      value={{
        onKeyDown,
        ref: triggerRef
      }}
    >
      <PopoverContext.Provider
        value={{
          triggerRef,
          isOpen,
          onOpenChange:
            setOpen
        }}
      >        {children}
      </PopoverContext.Provider>
    </ButtonContext.Provider>
  );
}

<HelpTrigger>
  <Button>
    Press ? for help
  </Button>
  <MyPopover>
    <Heading slot="title">
      Help
    </Heading>
    <div>
      Do you need help?
    </div>
  </MyPopover>
</HelpTrigger>

Hooks#

If you need to customize things further, such as accessing internal state or customizing DOM structure, you can drop down to the lower level Hook-based API. See usePopover for more details.