Popover is a floating view that contains a task related to the content on screen. It can be triggered when the user clicks or focuses on an element, typically Button or IconButton. It can also be triggered automatically, as in the case of user education. Popover is non-modal and can be dismissed by interacting with another part of the screen or an item within Popover.

Popover is most appropriate for desktop screens and can contain a variety of elements, such as Button and Images. Popover is also the container used to construct more complex elements like Dropdown and the board picker, pictured below, which allow people to choose the board to save a Pin to.

also known as Flyout

Web:

iOS:

Android:

Props

Component props
Name
Type
Default
anchor
Required
?HTMLElement
-

The reference element, typically Button or IconButton, that Popover uses to set its position.

onDismiss
Required
() => void
-

Callback fired when Popover requests to be closed. Must be used to control Popover’s on/off display state.

accessibilityDismissButtonLabel
string
-

Describes the dismiss button's purpose. See the dismiss button variant to learn more. Must be localized.

accessibilityLabel
string
-

Unique label to describe each Popover. Used for accessibility purposes.

children
React.Node
-

The content shown in Popover.

color
"blue" | "orange" | "red" | "white" | "darkGray"
-

The background color of Popover. See the color and caret variant to learn more.

id
string
-

Unique id to identify each Popover. Used for accessibility purposes.

idealDirection
"up" | "right" | "down" | "left"
-

Specifies the preferred position of Popover relative to its anchor element. See the ideal direction variant to learn more.

onKeyDown
({| event: SyntheticKeyboardEvent<HTMLElement> |}) => void
-

Callback for key stroke events allowing keyboard navigation in Popover's children.

positionRelativeToAnchor
boolean
-

Properly positions Popover relative to its anchor element. Set to false when used within Layer. See the with Layer variant to learn more.

role
"dialog" | "listbox" | "menu"
-

The underlying ARIA role for Popover. See the accessibility section for more info.

shouldFocus
boolean
-

Puts the focus on Popover when it’s triggered. See accessibility to learn more.

showCaret
boolean
-

Shows a caret on Popover. See the color and caret variant to learn more.

showDismissButton
boolean
-

Shows a dismiss button on Popover. See the dismiss button variant to learn more.

size
"xs" | "sm" | "md" | "lg" | "xl" | "flexible" | number
-

The maximum width of Popover. See the size variant to learn more.

Usage guidelines

When to use
  • Providing additional information for related context without cluttering the surface of a workflow.
  • Bringing attention to specific user interface elements for educational purposes. In this case, likely used with a Pulsar.
  • Accommodating a variety of features, such as Buttons, Images or SearchFields, that are not available in Dropdown.
When not to use
  • Displaying critical information that prevents users from accomplishing a task.
  • Displaying information out of context.
  • As a replacement for Tooltip.
  • For presenting a list of actions or options. Use Dropdown instead.

Best practices

Do

Use Popover to display a lightweight task related to the content on screen.

Don't

Use Popover to communicate critical information, such as an error or interaction feedback. Instead, use the error supplied directly to the form element. See related to learn more.

Do

Use Popover to educate users on a new or existing feature. When Popover is triggered automatically, like in the case of user education, be sure to use a blue background and include a caret pointing to the feature. See the color and caret variant to learn more.

Don't

Include a caret if Popover was triggered by user interaction, such as clicking or focusing on Button or IconButton.

Accessibility

Keyboard interaction

  • When Popover opens, focus moves to the first focusable element in the Popover container.
  • Popovers are also a focus trap, so users should only be able to interact with the content inside the Popover container.
  • Popover should always be dismissible using the ESC key. It could also be dismissed by interacting with another part of the screen, or by interacting with an element inside Popover.
  • When Popover is closed, focus returns to the anchor element that triggered Popover.

ARIA attributes

We recommend passing the following ARIA attribute to Popover for a better screen reader experience:

  • accessibilityLabel: describes the main purpose of a Popover for the screen reader. Should be unique and concise. For example, "Save to board" instead of "Popover". It populates aria-label.
  • accessibilityDismissButtonLabel: describes the purpose of the dismiss button on Popover for the screen reader. Should be clear and concise. For example, "Close board selection popover" instead of "Close".

To further assist screen readers, we recommend passing the following ARIA attributes to the anchor element:

  • accessibilityHaspopup: informs the screen reader that there’s a Popover attached to the anchor element. It populates aria-haspopup.
  • accessibilityExpanded: informs the screen reader whether Popover is currently open or closed. It populates aria-expanded.
  • accessibilityControls: match with the id of the associated Popover whose contents or visibility are controlled by the interactive component so that screen reader users can identify the relationship between elements. It populates aria-controls.

For the role prop, use:

  • 'dialog' if the Popover is a dialog that prompts the user to enter information or requires a response.
  • 'menu' if the Popover presents a list of choices to the user.
  • 'listbox' if the Popover is a widget that allows the user to select one or more items (whose role is option) from a list. May also include a search option.
import { useRef, useState } from 'react';
import {
  Popover,
  Box,
  Button,
  Flex,
  Layer,
  Text,
  SearchField,
  TapArea,
  Mask,
  Image,
} from 'gestalt';

function List({ title, setSelectedBoard, setOpen }) {
  return (
    <Flex direction="column" gap={{ column: 4, row: 0 }}>
      <Text color="default" size="100">
        {title}
      </Text>
      <Flex direction="column" gap={{ column: 4, row: 0 }}>
        {[
          [
            'https://i.ibb.co/s3PRJ8v/photo-1496747611176-843222e1e57c.webp',
            'Fashion',
            'Thumbnail image: a white dress with red flowers',
          ],
          [
            'https://i.ibb.co/swC1qpp/IMG-0494.jpg',
            'Food',
            'Thumbnail image: a paella with shrimp, green peas, red peppers and yellow rice',
          ],
          [
            'https://i.ibb.co/PFVF3JH/photo-1583847268964-b28dc8f51f92.webp',
            'Home',
            'Thumbnail image: a living room with a white couch, two paints in the wall and wooden furniture',
          ],
        ].map((data) => (
          <TapArea
            key={data[1]}
            onTap={() => {
              setSelectedBoard(data[1]);
              setOpen(false);
            }}
          >
            <Flex gap={{ row: 2, column: 0 }} alignItems="center">
              <Box height={50} width={50} overflow="hidden" rounding={2}>
                <Mask rounding={2}>
                  <Image
                    alt={data[2]}
                    color="rgb(231, 186, 176)"
                    naturalHeight={50}
                    naturalWidth={50}
                    src={data[0]}
                  />
                </Mask>
              </Box>
              <Text align="center" color="default" weight="bold">
                {data[1]}
              </Text>
            </Flex>
          </TapArea>
        ))}
      </Flex>
    </Flex>
  );
}

export default function DismissButton() {
  const [open, setOpen] = useState(false);
  const [selectedBoard, setSelectedBoard] = useState('Fashion');
  const anchorRef = useRef();

  return (
    <Flex alignItems="start" justifyContent="center" height="100%" width="100%">
      <Box padding={3}>
        <Flex alignItems="center" gap={{ row: 2, column: 0 }}>
          <Button
            accessibilityHaspopup
            accessibilityExpanded={open}
            accessibilityControls="main-example"
            color="white"
            iconEnd="arrow-down"
            onClick={() => setOpen(!open)}
            ref={anchorRef}
            size="lg"
            selected={open}
            text={selectedBoard}
          />
          <Button color="red" onClick={() => {}} size="lg" text="Save" />
        </Flex>
      </Box>
      {open && (
        <Layer>
          <Popover
            accessibilityLabel="Save to board"
            anchor={anchorRef.current}
            id="main-example"
            idealDirection="down"
            onDismiss={() => setOpen(false)}
            positionRelativeToAnchor={false}
            showDismissButton
            size="xl"
          >
            <Box width={360}>
              <Box flex="grow" marginEnd={4} marginStart={4} marginBottom={8}>
                <Flex direction="column" gap={{ column: 6, row: 0 }}>
                  <Text align="center" color="default" weight="bold">
                    Save to board
                  </Text>
                  <SearchField
                    accessibilityLabel="Search boards field"
                    id="searchField"
                    onChange={() => {}}
                    placeholder="Search boards"
                    size="lg"
                  />
                </Flex>
              </Box>
              <Box height={300} overflow="scrollY">
                <Box marginEnd={4} marginStart={4}>
                  <Flex direction="column" gap={{ column: 8, row: 0 }}>
                    <List
                      title="Top choices"
                      setSelectedBoard={setSelectedBoard}
                      setOpen={setOpen}
                    />
                    <List
                      title="All boards"
                      setSelectedBoard={setSelectedBoard}
                      setOpen={setOpen}
                    />
                  </Flex>
                </Box>
              </Box>
            </Box>
          </Popover>
        </Layer>
      )}
    </Flex>
  );
}

Localization

Be sure to localize any text elements within Popover, along with accessibilityLabel and accessibilityDismissButtonLabel. Note that localization can lengthen text by 20 to 30 percent.

Popovers with a dismiss button announce to assistive technologies that the button will dismiss the Popover. Localize the default label with DefaultLabelProvider. Learn more

Variants

Size

The maximum width of Popover. Popover has different size configurations:

  • "xs": 180px
  • "sm": 230px
  • "md": 284px
  • "lg": 320px
  • "xl": 360px
  • number: Use this prop to create custom-size Popovers in pixels.
  • flexible: Use this configuration for larger Popovers. Without a defined maximum width, Popover grows to fit the content in children.

We recommend using "xs" for education Popovers and "xl" for more complex Popovers. Avoid using other configurations whenever possible as they are legacy sizes.

Color and caret

When building in-product education, be sure to pass in color="blue" and showCaret="true", as seen in the first example, and use Experience HQ for the configuration. For Popovers that aren’t education, use the default color="white" and showCaret="false", as seen in the second example. Avoid using any other configurations as they are legacy colors.

Anchor

Popover requires a reference element, typically Button or IconButton, to set its position. The anchor ref can be directly set on the reference component itself. If the components don’t support ref, the anchor ref can be set to a parent Box.

Popover calculates its position based on the bounding box of the anchor. Therefore, the anchor ref should only include the trigger element itself, usually Button or IconButton, or the specific feature component that requires an educational Popover.

Dismiss button

We highly recommend including a dismiss button on all Popovers with showDismissButton. This improves accessibility and gives users an immediate action for closing Popover. A label for the button can be provided with the accessibilityDismissButtonLabel prop. Don't forget to localize this label as well.

Popovers with a dismiss button announce to assistive technologies that the button will dismiss the Popover with a default label of 'Close popover'. Localize the default label with DefaultLabelProvider. Learn more
import { useRef, useState } from 'react';
import {
  Popover,
  Box,
  Button,
  Flex,
  Layer,
  Text,
  SearchField,
  TapArea,
  Mask,
  Image,
} from 'gestalt';

function List({ title, setSelectedBoard, setOpen }) {
  return (
    <Flex direction="column" gap={{ column: 4, row: 0 }}>
      <Text color="default" size="100">
        {title}
      </Text>
      <Flex direction="column" gap={{ column: 4, row: 0 }}>
        {[
          [
            'https://i.ibb.co/s3PRJ8v/photo-1496747611176-843222e1e57c.webp',
            'Fashion',
            'Thumbnail image: a white dress with red flowers',
          ],
          [
            'https://i.ibb.co/swC1qpp/IMG-0494.jpg',
            'Food',
            'Thumbnail image: a paella with shrimp, green peas, red peppers and yellow rice',
          ],
          [
            'https://i.ibb.co/PFVF3JH/photo-1583847268964-b28dc8f51f92.webp',
            'Home',
            'Thumbnail image: a living room with a white couch, two paints in the wall and wooden furniture',
          ],
        ].map((data) => (
          <TapArea
            key={data[1]}
            onTap={() => {
              setSelectedBoard(data[1]);
              setOpen(false);
            }}
          >
            <Flex gap={{ row: 2, column: 0 }} alignItems="center">
              <Box height={50} width={50} overflow="hidden" rounding={2}>
                <Mask rounding={2}>
                  <Image
                    alt={data[2]}
                    color="rgb(231, 186, 176)"
                    naturalHeight={50}
                    naturalWidth={50}
                    src={data[0]}
                  />
                </Mask>
              </Box>
              <Text align="center" color="default" weight="bold">
                {data[1]}
              </Text>
            </Flex>
          </TapArea>
        ))}
      </Flex>
    </Flex>
  );
}

export default function DismissButton() {
  const [open, setOpen] = useState(false);
  const [selectedBoard, setSelectedBoard] = useState('Fashion');
  const anchorRef = useRef();

  return (
    <Flex alignItems="start" justifyContent="center" height="100%" width="100%">
      <Box padding={3}>
        <Flex alignItems="center" gap={{ row: 2, column: 0 }}>
          <Button
            accessibilityHaspopup
            accessibilityExpanded={open}
            accessibilityControls="main-example"
            color="white"
            iconEnd="arrow-down"
            onClick={() => setOpen(!open)}
            ref={anchorRef}
            size="lg"
            selected={open}
            text={selectedBoard}
          />
          <Button color="red" onClick={() => {}} size="lg" text="Save" />
        </Flex>
      </Box>
      {open && (
        <Layer>
          <Popover
            accessibilityLabel="Save to board"
            anchor={anchorRef.current}
            id="main-example"
            idealDirection="down"
            onDismiss={() => setOpen(false)}
            positionRelativeToAnchor={false}
            showDismissButton
            size="xl"
          >
            <Box width={360}>
              <Box flex="grow" marginEnd={4} marginStart={4} marginBottom={8}>
                <Flex direction="column" gap={{ column: 6, row: 0 }}>
                  <Text align="center" color="default" weight="bold">
                    Save to board
                  </Text>
                  <SearchField
                    accessibilityLabel="Search boards field"
                    id="searchField"
                    onChange={() => {}}
                    placeholder="Search boards"
                    size="lg"
                  />
                </Flex>
              </Box>
              <Box height={300} overflow="scrollY">
                <Box marginEnd={4} marginStart={4}>
                  <Flex direction="column" gap={{ column: 8, row: 0 }}>
                    <List
                      title="Top choices"
                      setSelectedBoard={setSelectedBoard}
                      setOpen={setOpen}
                    />
                    <List
                      title="All boards"
                      setSelectedBoard={setSelectedBoard}
                      setOpen={setOpen}
                    />
                  </Flex>
                </Box>
              </Box>
            </Box>
          </Popover>
        </Layer>
      )}
    </Flex>
  );
}

With Layer

Popover is typically used within Layer. Layer renders Popover outside the DOM hierarchy of the parent allowing it to overlay surrounding content. Popover calculates its position based on the bounding box of the anchor. Within Layer, Popover no longer shares a relative root with the anchor and requires positionRelativeToAnchor=false to properly calculate its position relative to the anchor element.

Using Layer with Popover eliminates the need to use z-index to solve stacking context conflicts. Popovers within Modals and Sheets with z-indexes don't require zIndex in Layer thanks to the built-in ScrollBoundaryContainer.

Ideal direction

Pass in idealDirection to specify the preferred position of Popover relative to the anchor, such as Button or IconButton, that triggered it.

Adjust the idealDirection as necessary to ensure the visibility of Popover and its contextual information. The default direction is "up", although Popover should be center-aligned directly below the element in most cases. The actual position may change given the available space around the anchor element.

Within scrolling containers

ScrollBoundaryContainer is needed for proper positioning when Popover is anchored to an element that is located within a scrolling container. The use of ScrollBoundaryContainer ensures Popover remains attached to its anchor when scrolling.

Writing

Do
  • Be clear and predictable so that people anticipate what will happen when they interact with an item.
  • Focus on the action by beginning with a verb.
  • Use succinct and scannable language.
  • Use sentence case while always capitalizing the word “Pin.”
Don't
  • Describe the interface element, like “button,” “icon” or “menu” in education messaging, unless it’s absolutely necessary for clarity.
  • Use words like “click” or “tap” in education messaging, if possible, or assume universal accessibility.
  • Use Popover to communicate critical information, such as an error or interaction feedback.

Component quality checklist

Component quality checklist
Quality item
Status
Status description
Figma Library
Partially ready
Component is live in Figma, however may not be available for all platforms.
Responsive Web
Ready
Component is available in code for web and mobile web.
iOS
Component is not currently available in code for iOS.
Android
Component is not currently available in code for Android.

Dropdown
Dropdown is an element constructed using Popover as its container. Use Dropdown to display a list of actions or options in a Popover.

Toast
Toast provides feedback on an interaction. One example of Toast is the confirmation that appears when a Pin has been saved. Toasts appear at the bottom of a desktop screen or top of a mobile screen instead of being attached to any particular element on the interface.

Tooltip
Tooltip describes the function of an interactive element, typically IconButton, on hover. While Popovers offer broader content options, such as Button and Images, Tooltips are purely text-based.

Layer
Layer renders Popover outside the DOM hierarchy of the parent and prevents surrounding components overlaying Popover. See the with Layer variant to learn more.

ScrollBoundaryContainer
ScrollBoundaryContainer is needed for proper positioning when Popover is anchored to an element that is located within a scrolling container. The use of ScrollBoundaryContainer ensures that Popover remains attached to its anchor when scrolling. See the within scrolling containers variant to learn more.