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

Figma:

Responsive:

Adaptive:

Props

Component props
Name
Type
Default
anchor
Required
HTMLElement | null | undefined
-

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
"Popover"

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

children
React.Node
-

The content shown in Popover.

color
"white" | "darkGray"
"white"

This field is deprecated and will be removed soon. Please do not use. See PopoverEducational.

disablePortal
boolean
true

EXPERIMENTAL: Disables portalling and Popover will be under the DOM hierarchy of the parent component.

forceDirection
boolean
false

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

hideWhenReferenceHidden
boolean
true

EXPERIMENTAL: Whether to hide Popover when reference element gets out of viewport.

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
(arg1: { event: React.KeyboardEvent<HTMLElement> }) => void
-

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

positionRelativeToAnchor
boolean
true

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" | "tooltip"
"dialog"

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

scrollBoundary
HTMLElement
-

EXPERIMENTAL: Reference to a parent of the anchor element, relative to which Popover shifts or flips its position.

shouldFocus
boolean
true

Puts the focus on Popover when it’s triggered. See accessibility 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
"sm"

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.
  • 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.

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.
  • 'tooltip' if the Popover is a simple contextual text bubble that displays a description on a feature.
import { useRef, useState } from 'react';
import {
  Box,
  Button,
  Flex,
  Image,
  Mask,
  Popover,
  SearchField,
  TapArea,
  Text,
} from 'gestalt';

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

  return (
    <Flex alignItems="start" height="100%" justifyContent="center" width="100%">
      <Box padding={3}>
        <Flex alignItems="center" gap={{ row: 2, column: 0 }}>
          <Button
            ref={anchorRef}
            accessibilityControls="a11l-example"
            accessibilityExpanded={open}
            accessibilityHaspopup
            color="white"
            iconEnd="arrow-down"
            onClick={() => {
              setOpen((o) => !o);
            }}
            selected={open}
            size="lg"
            text={selectedBoard}
          />
          <Button color="red" onClick={() => {}} size="lg" text="Save" />
        </Flex>
      </Box>

      {open && (
        <Popover
          accessibilityLabel="Save to board"
          anchor={anchorRef.current}
          forceDirection
          id="a11l-example"
          idealDirection="down"
          onDismiss={() => setOpen(false)}
          shouldFocus={false}
          // positionRelativeToAnchor={false}
          showDismissButton
          size="xl"
        >
          <Box width={300}>
            <Box flex="grow" marginBottom={8} marginEnd={4} marginStart={4}>
              <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 }}>
                  <Flex direction="column" gap={{ column: 4, row: 0 }}>
                    <Text color="default" size="100">
                      Top choices
                    </Text>
                    {[
                      {
                        url: 'https://i.ibb.co/s3PRJ8v/photo-1496747611176-843222e1e57c.webp',
                        title: 'Fashion',
                        alt: 'Thumbnail image: a white dress with red flowers',
                      },
                      {
                        url: 'https://i.ibb.co/swC1qpp/IMG-0494.jpg',
                        title: 'Food',
                        alt: 'Thumbnail image: a paella with shrimp, green peas, red peppers and yellow rice',
                      },
                      {
                        url: 'https://i.ibb.co/PFVF3JH/photo-1583847268964-b28dc8f51f92.webp',
                        title: 'Home',
                        alt: 'Thumbnail image: a living room with a white couch, two paints in the wall and wooden furniture',
                      },
                    ].map(({ alt, title: imageTitle, url }) => (
                      <TapArea
                        key={imageTitle}
                        onTap={() => {
                          setSelectedBoard(imageTitle);
                          setOpen(false);
                        }}
                      >
                        <Flex alignItems="center" gap={{ row: 2, column: 0 }}>
                          <Box
                            height={50}
                            overflow="hidden"
                            rounding={2}
                            width={50}
                          >
                            <Mask rounding={2}>
                              <Image
                                alt={alt}
                                color="rgb(231, 186, 176)"
                                naturalHeight={50}
                                naturalWidth={50}
                                src={url}
                              />
                            </Mask>
                          </Box>
                          <Text align="center" color="default" weight="bold">
                            {imageTitle}
                          </Text>
                        </Flex>
                      </TapArea>
                    ))}
                  </Flex>
                </Flex>
              </Box>
            </Box>
          </Box>
        </Popover>
      )}
    </Flex>
  );
}

Localization

Be sure to localize all text strings. Note that localization can lengthen text by 20 to 30 percent.

Note that accessibilityDismissButtonLabel is optional as DefaultLabelProvider provides default strings. Use custom labels if they need to be more specific.

Popover depends on DefaultLabelProvider for internal text strings. Localize the texts via DefaultLabelProvider. Learn more
import { useEffect, useRef, useState } from 'react';
import {
  Box,
  Button,
  DefaultLabelProvider,
  Flex,
  Layer,
  Popover,
  Text,
} from 'gestalt';

export default function Example() {
  const [open, setOpen] = useState(false);
  const anchorRef = useRef(null);

  useEffect(() => {
    setOpen(true);
  }, []);

  return (
    <DefaultLabelProvider
      labels={{
        Popover: {
          accessibilityDismissButtonLabel: 'Popover verwerfen.',
        },
      }}
    >
      <Flex height="100%" width="100%">
        <Box
          alignItems="start"
          display="flex"
          justifyContent="center"
          padding={2}
          width="100%"
        >
          <Button
            ref={anchorRef}
            color="red"
            onClick={() => setOpen((value) => !value)}
            size="lg"
            text="Sparen"
          />
        </Box>
        {open && (
          <Layer>
            <Popover
              accessibilityLabel="An Bord speichern."
              anchor={anchorRef.current}
              idealDirection="down"
              onDismiss={() => setOpen(false)}
              positionRelativeToAnchor
              showDismissButton
              size={240}
            >
              <Box
                alignItems="center"
                display="flex"
                height={200}
                justifyContent="center"
                width={240}
              >
                <Text align="center">Inhalt</Text>
              </Box>
            </Popover>
          </Layer>
        )}
      </Flex>
    </DefaultLabelProvider>
  );
}

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.

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.

import { useEffect, useRef, useState } from 'react';
import { Box, Button, Flex, Layer, Popover, Text } from 'gestalt';

export default function Example() {
  const [open, setOpen] = useState(false);
  const anchorRef = useRef(null);

  useEffect(() => {
    setOpen(true);
  }, []);

  return (
    <Flex height="100%" width="100%">
      <Box
        alignItems="start"
        display="flex"
        justifyContent="center"
        padding={2}
        width="100%"
      >
        <Button
          ref={anchorRef}
          color="red"
          onClick={() => setOpen((value) => !value)}
          size="lg"
          text="Save"
        />
      </Box>
      {open && (
        <Layer>
          <Popover
            anchor={anchorRef.current}
            idealDirection="down"
            onDismiss={() => {}}
            positionRelativeToAnchor
            size={400}
          >
            <Box
              alignItems="center"
              display="flex"
              height={200}
              justifyContent="center"
              width={300}
            >
              <Text align="center">Content</Text>
            </Box>
          </Popover>
        </Layer>
      )}
    </Flex>
  );
}

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.

import { useRef, useState } from 'react';
import {
  Box,
  Button,
  Flex,
  Image,
  Mask,
  Popover,
  SearchField,
  TapArea,
  Text,
} from 'gestalt';

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

  return (
    <Flex alignItems="start" height="100%" justifyContent="center" width="100%">
      <Box padding={3}>
        <Flex alignItems="center" gap={{ row: 2, column: 0 }}>
          <Button
            ref={anchorRef}
            accessibilityControls="a11l-example"
            accessibilityExpanded={open}
            accessibilityHaspopup
            color="white"
            iconEnd="arrow-down"
            onClick={() => {
              setOpen((o) => !o);
            }}
            selected={open}
            size="lg"
            text={selectedBoard}
          />
          <Button color="red" onClick={() => {}} size="lg" text="Save" />
        </Flex>
      </Box>

      {open && (
        <Popover
          accessibilityLabel="Save to board"
          anchor={anchorRef.current}
          forceDirection
          id="a11l-example"
          idealDirection="down"
          onDismiss={() => setOpen(false)}
          shouldFocus={false}
          // positionRelativeToAnchor={false}
          showDismissButton
          size="xl"
        >
          <Box width={300}>
            <Box flex="grow" marginBottom={8} marginEnd={4} marginStart={4}>
              <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 }}>
                  <Flex direction="column" gap={{ column: 4, row: 0 }}>
                    <Text color="default" size="100">
                      Top choices
                    </Text>
                    {[
                      {
                        url: 'https://i.ibb.co/s3PRJ8v/photo-1496747611176-843222e1e57c.webp',
                        title: 'Fashion',
                        alt: 'Thumbnail image: a white dress with red flowers',
                      },
                      {
                        url: 'https://i.ibb.co/swC1qpp/IMG-0494.jpg',
                        title: 'Food',
                        alt: 'Thumbnail image: a paella with shrimp, green peas, red peppers and yellow rice',
                      },
                      {
                        url: 'https://i.ibb.co/PFVF3JH/photo-1583847268964-b28dc8f51f92.webp',
                        title: 'Home',
                        alt: 'Thumbnail image: a living room with a white couch, two paints in the wall and wooden furniture',
                      },
                    ].map(({ alt, title: imageTitle, url }) => (
                      <TapArea
                        key={imageTitle}
                        onTap={() => {
                          setSelectedBoard(imageTitle);
                          setOpen(false);
                        }}
                      >
                        <Flex alignItems="center" gap={{ row: 2, column: 0 }}>
                          <Box
                            height={50}
                            overflow="hidden"
                            rounding={2}
                            width={50}
                          >
                            <Mask rounding={2}>
                              <Image
                                alt={alt}
                                color="rgb(231, 186, 176)"
                                naturalHeight={50}
                                naturalWidth={50}
                                src={url}
                              />
                            </Mask>
                          </Box>
                          <Text align="center" color="default" weight="bold">
                            {imageTitle}
                          </Text>
                        </Flex>
                      </TapArea>
                    ))}
                  </Flex>
                </Flex>
              </Box>
            </Box>
          </Box>
        </Popover>
      )}
    </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 disablePortal=true to disable its internal Layer.

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

import { Fragment, useEffect, useRef, useState } from 'react';
import {
  Box,
  Button,
  FixedZIndex,
  Flex,
  Image,
  Layer,
  Mask,
  OverlayPanel,
  Popover,
  SearchField,
  TapArea,
  Text,
  TextArea,
} from 'gestalt';

const images = [
  {
    url: 'https://i.ibb.co/s3PRJ8v/photo-1496747611176-843222e1e57c.webp',
    title: 'Fashion',
    alt: 'Thumbnail image: a white dress with red flowers',
  },
  {
    url: 'https://i.ibb.co/swC1qpp/IMG-0494.jpg',
    title: 'Food',
    alt: 'Thumbnail image: a paella with shrimp, green peas, red peppers and yellow rice',
  },
  {
    url: 'https://i.ibb.co/PFVF3JH/photo-1583847268964-b28dc8f51f92.webp',
    title: 'Home',
    alt: 'Thumbnail image: a living room with a white couch, two paints in the wall and wooden furniture',
  },
];

function SearchBoardField() {
  const ref = useRef(null);

  useEffect(() => {
    if (ref.current) ref.current.focus();
  }, []);

  return (
    <SearchField
      ref={ref}
      accessibilityLabel="Search boards field"
      id="searchField"
      onChange={() => {}}
      placeholder="Search boards"
      size="lg"
    />
  );
}

function List({ handleImageTap, title }) {
  return (
    <Flex direction="column" gap={{ column: 4, row: 0 }}>
      <Text color="default" size="100">
        {title}
      </Text>
      <Flex direction="column" gap={{ column: 4, row: 0 }}>
        {images.map(({ alt, title: imageTitle, url }) => (
          <TapArea
            key={imageTitle}
            onTap={() => {
              handleImageTap(imageTitle);
            }}
            rounding={2}
          >
            <Flex alignItems="center" gap={{ row: 2, column: 0 }}>
              <Box height={50} overflow="hidden" rounding={2} width={50}>
                <Mask rounding={2}>
                  <Image
                    alt={alt}
                    color="rgb(231, 186, 176)"
                    naturalHeight={50}
                    naturalWidth={50}
                    src={url}
                  />
                </Mask>
              </Box>
              <Text align="center" color="default" weight="bold">
                {imageTitle}
              </Text>
            </Flex>
          </TapArea>
        ))}
      </Flex>
    </Flex>
  );
}

function SelectBoard() {
  const [openPopover, setOpenPopover] = useState(false);
  const [selectedBoard, setSelectedBoard] = useState('Fashion');
  const anchorRef = useRef(null);

  const handleImageTap = (imageTitle) => {
    setSelectedBoard(imageTitle);
    setOpenPopover(false);
  };

  return (
    <Fragment>
      <Flex direction="column" gap={{ column: 2, row: 0 }}>
        <Text size="100">Board</Text>
        <Button
          ref={anchorRef}
          accessibilityControls="popover-search-board-3"
          accessibilityExpanded={openPopover}
          accessibilityHaspopup
          iconEnd="arrow-down"
          onClick={() => setOpenPopover(!openPopover)}
          text={selectedBoard}
        />
      </Flex>
      {openPopover && (
        <Layer>
          <Popover
            anchor={anchorRef.current}
            id="popover-search-board-3"
            idealDirection="down"
            onDismiss={() => setOpenPopover(false)}
            positionRelativeToAnchor={false}
            showDismissButton
            size="xl"
          >
            <Box width={360}>
              <Box flex="grow" marginBottom={8} marginEnd={4} marginStart={4}>
                <Flex direction="column" gap={{ column: 6, row: 0 }}>
                  <Text align="center" color="default" weight="bold">
                    Save to board
                  </Text>
                  <SearchBoardField />
                </Flex>
              </Box>
              <Box height={300} overflow="scrollY">
                <Box marginEnd={4} marginStart={4}>
                  <Flex direction="column" gap={{ column: 8, row: 0 }}>
                    <List handleImageTap={handleImageTap} title="Top choices" />
                    <List handleImageTap={handleImageTap} title="All boards" />
                  </Flex>
                </Box>
              </Box>
            </Box>
          </Popover>
        </Layer>
      )}
    </Fragment>
  );
}

export default function Example() {
  const [showSheet, setShowSheet] = useState(false);

  return (
    <Box padding={6}>
      <Button
        accessibilityControls="popover-overlaypanel"
        accessibilityExpanded={showSheet}
        accessibilityHaspopup
        onClick={() => setShowSheet(true)}
        size="lg"
        text="Edit Pin"
      />

      {showSheet && (
        <Layer zIndex={new FixedZIndex(11)}>
          <OverlayPanel
            accessibilityDismissButtonLabel="Close edit Pin overlay panel"
            accessibilityLabel="Edit your Pin details"
            footer={
              <Flex>
                <Flex.Item flex="grow">
                  <Button
                    color="white"
                    onClick={() => setShowSheet(false)}
                    size="lg"
                    text="Delete"
                  />
                </Flex.Item>
                <Flex gap={{ column: 0, row: 2 }}>
                  <Button
                    onClick={() => setShowSheet(false)}
                    size="lg"
                    text="Cancel"
                  />
                  <Button
                    color="red"
                    onClick={() => setShowSheet(false)}
                    size="lg"
                    text="Done"
                    type="submit"
                  />
                </Flex>
              </Flex>
            }
            heading="Edit Pin"
            onDismiss={() => setShowSheet(false)}
            size="lg"
          >
            <Box
              display="flex"
              height={400}
              id="popover-overlaypanel"
              paddingX={8}
            >
              <Flex gap={{ row: 8, column: 0 }} width="100%">
                <Box paddingX={2} rounding={4} width={200}>
                  <Mask rounding={4}>
                    <Image
                      alt="Tropic greens: The taste of Petrol and Porcelain | Interior design, Vintage Sets and Unique Pieces agave"
                      color="rgb(231, 186, 176)"
                      naturalHeight={751}
                      naturalWidth={564}
                      src="https://i.ibb.co/7bQQYkX/stock2.jpg"
                    />
                  </Mask>
                </Box>
                <Flex.Item flex="grow">
                  <Flex direction="column" gap={{ column: 8, row: 0 }}>
                    <SelectBoard />
                    <TextArea
                      id="note"
                      label="Note"
                      onChange={() => {}}
                      placeholder="Add note"
                      value=""
                    />
                  </Flex>
                </Flex.Item>
              </Flex>
            </Box>
          </OverlayPanel>
        </Layer>
      )}
    </Box>
  );
}

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.

import { useRef, useState } from 'react';
import {
  Box,
  ButtonToggle,
  Flex,
  Label,
  Layer,
  Popover,
  SelectList,
  Switch,
  Text,
} from 'gestalt';

export default function Example() {
  const [open, setOpen] = useState(false);
  const [direction, setDirection] = useState('down');
  const [forceDirection, setForceDirection] = useState(false);

  const anchorRef = useRef(null);

  return (
    <Flex
      alignItems="center"
      direction="column"
      gap={6}
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <Flex gap={6}>
        <SelectList
          id="idealDir"
          label="idealDirection"
          onChange={({ value }) => {
            const dirValue = value;
            setDirection(dirValue);
          }}
        >
          {[
            { label: 'Up', value: 'up' },
            { label: 'Down', value: 'down' },
            { label: 'Right', value: 'right' },
            { label: 'Left', value: 'left' },
          ].map(({ label, value }) => (
            <SelectList.Option key={label} label={label} value={value} />
          ))}
        </SelectList>
        <Flex alignItems="center" direction="column" gap={2}>
          <Label htmlFor="idealDirLabel">
            <Text size="100">
              {forceDirection
                ? 'forceDirection: true'
                : 'forceDirection: false'}
            </Text>
          </Label>

          <Switch
            id="idealDirSwitch"
            onChange={() => setForceDirection((currVal) => !currVal)}
            switched={forceDirection}
          />
        </Flex>
      </Flex>
      <ButtonToggle
        ref={anchorRef}
        onClick={() => setOpen((value) => !value)}
        selected={open}
        size="lg"
        text={open ? 'Showing Popover' : 'Open Popover'}
      />
      {open && (
        <Layer>
          <Popover
            anchor={anchorRef.current}
            forceDirection={forceDirection}
            idealDirection={direction}
            onDismiss={() => {}}
            positionRelativeToAnchor={false}
          >
            <Box
              alignItems="center"
              display="flex"
              height={100}
              justifyContent="center"
            >
              <Text align="center">Content</Text>
            </Box>
          </Popover>
        </Layer>
      )}
    </Flex>
  );
}

Within scrolling containers

Popover always remains attached to its anchor when scrolling. However Popover may float outside of the scrolling container without adjusting itself (shift / flip) when reached to the edges if:

  • Popover is rendered through portal using
    • disablePortal={true} prop
    • Layer component
  • the scrolling container is not positioned relatively (position: relative)

For some use cases that can be the indeneded behavior.

If Popover has to respect the edges of its scrolling container, the scrolling has to have position CSS rule set to relative. Popover's disablePortal prop is by default true. That's why in this case disablePortal should not be set to false.

import { useEffect, useRef, useState } from 'react';
import { Box, ButtonLink, Flex, Popover, Text } from 'gestalt';

export default function Example() {
  const [open, setOpen] = useState(false);
  const anchorRef = useRef(null);
  const viewRef = useRef(null);

  useEffect(() => {
    setOpen(true);
  }, []);

  return (
    <Box
      alignItems="center"
      color="lightWash"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={6}
      width="100%"
    >
      <Box
        height={200}
        overflow="auto"
        position="relative" // this prevents Popover from overflowing the container
      >
        <Box
          ref={viewRef}
          color="default"
          padding={4}
          position="relative"
          width={600}
        >
          <Flex alignItems="center" gap={{ column: 0, row: 4 }}>
            <Box width={220}>
              <Text>
                You need to add your data source URL to Pinterest so we can
                access your data source file and create Pins for your products.
                Before you do this, make sure you have prepared your data source
                and that you have claimed your website. If there are any errors
                with your data source file, you can learn how to troubleshoot
                them below. After you click Create Pins, you&apos;ll land back
                at the main data source page while your feed is being processed.
                Wait for a confirmation email from Pinterest about the status of
                your data source submission.
              </Text>
            </Box>

            <ButtonLink
              ref={anchorRef}
              href="https://help.pinterest.com/en/business/article/data-source-ingestion"
              iconEnd="visit"
              onClick={() => setOpen(false)}
              size="lg"
              target="blank"
              text="Help"
            />
          </Flex>

          {open && (
            <Popover
              anchor={anchorRef.current}
              disablePortal
              idealDirection="right"
              onDismiss={() => {}}
              size="xs"
            >
              <Box
                alignItems="center"
                display="flex"
                height={100}
                justifyContent="center"
              >
                <Text align="center">Content</Text>
              </Box>
            </Popover>
          )}
        </Box>
      </Box>
    </Box>
  );
}

Visibility on page load

Popover's positioning algorithm requires that the anchor element renders before Popover is rendered. If Popover should be visible on page load, use useEffect to toggle the visibility after the first render.

import { useEffect, useRef, useState } from 'react';
import { Box, Flex, IconButton, Layer, Popover, Text } from 'gestalt';

export default function Example() {
  const [open, setOpen] = useState(false);
  const anchorRef = useRef(null);

  useEffect(() => {
    setOpen(true);
  }, []);

  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <IconButton
        ref={anchorRef}
        accessibilityLabel="Default IconButton"
        icon="pin"
        iconColor="darkGray"
        onClick={() => {}}
        size="lg"
      />

      {open && (
        <Layer>
          <Popover
            anchor={anchorRef.current}
            idealDirection="down"
            onDismiss={() => {}}
            positionRelativeToAnchor={false}
            size="xs"
          >
            <Box
              alignItems="center"
              display="flex"
              height={100}
              justifyContent="center"
            >
              <Text align="center">Content</Text>
            </Box>
          </Popover>
        </Layer>
      )}
    </Flex>
  );
}

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
Ready
Component is available in Figma for web and mobile web.
Responsive Web
Ready
Component responds to changing viewport sizes in web and mobile web.
Issues
This component has known issues.

PopoverEducational
Popover used for education or onboarding experiences.

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.