Popover

Jump to Props

Provide additional context with a fully customisable popover

A Popover component is used to display additional content in a small floating window when a specific element clicked or given focus.

Import

import { Popover } from "@vitality-ds/components";
<Popover
  triggerElement={<Button appearance="primary">Open Popover</Button>}
  content={
    <Stack>
      <Typography variant="sectionTitle">Section Title</Typography>
      <Typography variant="body">Body</Typography>
    </Stack>
  }
/>

Triggering the Popover

There are two ways to activate the Popover component, and it can be used in either a controlled or uncontrolled manner.

Un-controlled

If you pass a trigger element using the triggerElement prop, the state of the Popover component will be automatically managed. However, please note that this method requires the trigger element to be an HTML button element that can receive a ref.

<Popover
  triggerElement={
    <IconButton icon={<AdminNote />} tooltipContent="Open Popover" />
  }
  content={
    <Stack>
      <Typography variant="sectionTitle">Section Title</Typography>
      <Typography variant="body">Body</Typography>
    </Stack>
  }
/>

Controlled

When you pass a boolean value to the isOpen prop, the internal state management of the Popover component will be disabled. This means that the responsibility of opening and closing the popover now falls on the user.

() => {
  const [isOpen, setIsOpen] = React.useState(false);

  return (
    <>
      <Popover
        isOpen={isOpen}
        triggerElement={
          <Button appearance="primary" onClick={() => setIsOpen(!isOpen)}>
            Toggle Popover
          </Button>
        }
        content={
          <Stack>
            <Typography variant="sectionTitle">Section Title</Typography>
            <Typography variant="body">Body</Typography>
          </Stack>
        }
      />
    </>
  );
};

Positioning the Popover

Although all popovers are "collision aware", you can still indicate the preference for which side of its trigger it appears, and how to align itself around that side. The collision detection safeguards against popover elements disappearing outside of the viewport and will opt for the opposide side value. For instance, side="top" will switch to side="bottom" if a popover cannot fit above its trigger. These positions will also update as the user scrolls or resizes the window.

Side

When deciding which side or alignment to use, consider what would make the most sense to a user based on the contents of the Popover. For example, displaying search results would likely go to the bottom side so that the results are shown under the search term, or a piece of additional information (like in an informational popover) may make more sense to the right side. If you're unsure, you can leave the default.

() => {
  const [interactedOutside, setInteractedOutside] = React.useState("");
  const [side, setSide] = React.useState("top");
  const [align, setAlign] = React.useState("start");
  return (
    <DocsBox css={{ width: "100%", padding: "$xl" }}>
      <Stack direction="horizontal" align="center" justify="between">
        <Stack direction="horizontal">
          <FormField
            type="radioButtons"
            id="side"
            label="Side"
            onChange={(newValue) => setSide(newValue)}
            value={side}
            inputProps={{
              name: "side",
              options: [
                { label: "left", id: "left", value: "left" },
                { label: "right", id: "right", value: "right" },
                { label: "top", id: "top", value: "top" },
                { label: "bottom", id: "bottom", value: "bottom" },
              ],
            }}
          />
          <FormField
            type="radioButtons"
            label="Align"
            id="align"
            onChange={(newValue) => setAlign(newValue)}
            value={align}
            inputProps={{
              name: "align",
              options: [
                { label: "start", id: "start", value: "start" },
                { label: "center", id: "center", value: "center" },
                { label: "end", id: "end", value: "end" },
              ],
            }}
          />
        </Stack>
        <Popover
          isOpen
          onInteractOutside={(event) => event.preventDefault()}
          triggerElement={<Button>Trigger element</Button>}
          side={side}
          align={align}
          content={
            <Typography variant="body">
              I'm on top unless I won't fit!
            </Typography>
          }
        />
      </Stack>
    </DocsBox>
  );
};

Align

Setting the Popover's align property will determine the alignment of the popover to its side. For instance, to display a popover underneath a search field, you'd want the popover placed on the bottom side of the trigger (the text input) and aligned to the start (ie. left).

() => {
  const [interactedOutside, setInteractedOutside] = React.useState("");

  return (
    <Popover
      isOpen
      onInteractOutside={(event) => event.preventDefault()}
      triggerElement={<TextInput placeholder="Search for a patient..." />}
      side="bottom"
      align="start"
      content={
        <Typography variant="body" color="lowContrast">
          Search results appear here
        </Typography>
      }
    />
  );
};

Focus management

By default, when used in an uncontrolled manner, the Popover component will automatically close when the user interacts with elements outside of its content (e.g., clicks outside the popover). You can customize this behaviour using the onInteractOutside prop, you are able to access the event through this function.

() => {
  const [interactedOutside, setInteractedOutside] = React.useState("");

  return (
    <Popover
      onInteractOutside={(event) => event.preventDefault()}
      triggerElement={<Button appearance="primary">Toggle Popover</Button>}
      content={
        <Stack>
          <Typography variant="sectionTitle">Section Title</Typography>
          <Typography variant="body">Body</Typography>
        </Stack>
      }
    />
  );
};

The onInteractOutside prop also enables you to replicate the default behaviour of the Popover component when used in a controlled state, where the popover automatically closes upon outside interaction.

() => {
  const [isOpen, setIsOpen] = React.useState(false);

  return (
    <>
      <Popover
        isOpen={isOpen}
        onInteractOutside={() => setIsOpen(false)}
        triggerElement={
          <Button appearance="primary" onClick={() => setIsOpen(true)}>
            Open Popover
          </Button>
        }
        content={
          <Stack>
            <Typography variant="sectionTitle">Section Title</Typography>
            <Typography variant="body">Body</Typography>
          </Stack>
        }
      />
    </>
  );
};

Props

align

Description

The preferred alignment against the anchor. May change when collisions occur

Type

"center" | "end" | "start"

Default Value

"center"

contentRequired

Description

The component that pops out when the popover is open.

Type

ReactNode

isOpen

Description

The controlled open state of the popover. false is closed, true is open.

Type

boolean

onInteractOutside

Description

The function run when focus exits the popover's container

Type

(event: PointerDownOutsideEvent | FocusOutsideEvent) => void

side

Description

The preferred side of the anchor to render against when open. Will be reversed when collisions occur.

Type

"bottom" | "left" | "right" | "top"

Default Value

"bottom"

triggerElementRequired

Description

The element that toggles the popover when clicked

Type

ReactNode

© 2025