OverlayPanels are surfaces that allow users to view optional information or complete sub-tasks in a workflow while keeping the context of the current page. The most common example of OverlayPanel displays content in a panel that opens from the side of the screen for the user to read or input information. OverlayPanels have default, internal padding for content.

also known as Drawer, Panel, Tray, Sheet

Figma:

Responsive:

Adaptive:

Props

Component props
Name
Type
Default
accessibilityLabel
Required
string
-

Supply a short, descriptive label for screen-readers to contextualize the purpose of OverlayPanel. See the Accessibility section for more info.

children
Required
React.Node | ((arg1: { onDismissStart: () => void }) => React.Node)
-

Supply the container element(s) or render prop that will be used as OverlayPanel's main content. See the animation variant for info on how to add exit animations to OverlayPanel content..

onDismiss
Required
() => void
-

Callback fired when the OverlayPanel is dismissed by clicking on the Dismiss button, pressing the ESC key, or clicking on the backdrop outside of the OverlayPanel (if closeOnOutsideClick is true).

accessibilityDismissButtonLabel
string
-

Supply a short, descriptive label for screen-readers as a text alternative to the Dismiss button. See the Accessibility section for more info.

closeOnOutsideClick
boolean
true

Indicate whether clicking on the backdrop (gray area) outside of OverlayPanel will automatically close it. See the outside click variant for more info.

dismissConfirmation
{
  message?: string;
  subtext?: string;
  primaryAction?: {
    accessibilityLabel?: string;
    text?: string;
    onClick?: (arg1: {
      event:
        | React.MouseEvent<HTMLButtonElement>
        | React.MouseEvent<HTMLAnchorElement>
        | React.KeyboardEvent<HTMLAnchorElement>
        | React.KeyboardEvent<HTMLButtonElement>;
    }) => void;
  };
  secondaryAction?: {
    accessibilityLabel?: string;
    text?: string;
    onClick?: (arg1: {
      event:
        | React.MouseEvent<HTMLButtonElement>
        | React.MouseEvent<HTMLAnchorElement>
        | React.KeyboardEvent<HTMLAnchorElement>
        | React.KeyboardEvent<HTMLButtonElement>;
    }) => void;
  };
}
-

When supplied, it will disable component-controlled dismiss actions (ESC key press, backdrop click, or built-in dismiss IconButtons) and launch a confirmation Popover next to the dismiss IconButton requesting user confirmation before proceding. Pass an empty object to use the default text and labels. See the dismiss confirmation variant to learn more.

React.Node | ((arg1: { onDismissStart: () => void }) => React.Node)
-

Supply the container element(s) or render prop that will be used as OverlayPanel's custom footer. See the footer variant for more info..

heading
string
-

The text used for OverlayPanel's heading. Be sure to localize this text. See the heading variant for more info.

onAnimationEnd
(arg1: { animationState: "in" | "out" }) => void
-

Callback fired when the OverlayPanel in/out animations end. See the animation variant to learn more.

size
"sm" | "md" | "lg"
"sm"

Determine the width of the OverlayPanel component. See the size variant for more info.

subHeading
React.Node | ((arg1: { onDismissStart: () => void }) => React.Node)
-

Supply the container element(s) or render prop that will be used as OverlayPanel's sub-heading docked under the heading. See the sub-heading variant for more info.

Usage guidelines

When to use
  • Performing an optional sub-task within a larger task.
  • Quick bulk edits on info from a Table.
  • Presenting help info while maintaining the current page and its context.
When not to use
  • Getting user confirmation on an action. Use a Modal instead.
  • Displaying system errors or notices. Consider a BannerCallout instead.
  • Any time a separate, designated URL is desired.

Best practices

Do

Use OverlayPanel for sub-tasks within a large workflow that are optional, like creating a new audience list while creating a campaign.

Do

Use OverlayPanel for quick edits within libraries or tables of content where you expect users to be making multiple edits in one session.

Do

Use the same size OverlayPanel on a product surface. For example, if filling out a form requires multiple OverlayPanels to be opened to complete different subtasks, then all OverlayPanels in that form should be the same width. When in doubt, pick the widest size needed for the entire flow.

Don't

Use OverlayPanel for required tasks or main tasks, like logging in. Put those tasks within the content of the page instead.

Don't

Use OverlayPanel if edits or sub-tasks require more than two steps. Bring users to a full page experience or consider using Accordions to section out content.

Don't

Use OverlayPanel to confirm actions or display alerts. Use a Modal or Toast instead.

Accessibility

Labels

  • accessibilityDismissButtonLabel: provides a short, descriptive label for screen readers as a text alternative to the Dismiss button. Populates the aria-label attribute on the Dismiss button.
  • accessibilityLabel: provides a short, descriptive label for screen readers to contextualize the purpose of OverlayPanel. Please don’t repeat the same text being passed in the heading prop, but instead provide something that summarizes the OverlayPanel’s purpose. For instance, if the heading is "Pin Builder", the accessibilityLabel can be "Create a new Pin". Populates the aria-label attribute on the entire dialog.
import { Fragment, useState } from 'react';
import {
  Box,
  Button,
  Checkbox,
  CompositeZIndex,
  Fieldset,
  FixedZIndex,
  Flex,
  Layer,
  OverlayPanel,
  RadioButton,
  Text,
  TextField,
} from 'gestalt';

export default function Example() {
  const [showComponent, setShowComponent] = useState(true);
  const HEADER_ZINDEX = new FixedZIndex(10);
  const sheetZIndex = new CompositeZIndex([HEADER_ZINDEX]);

  const footer = (
    <OverlayPanel.DismissingElement>
      {({ onDismissStart }) => (
        <Flex alignItems="center" justifyContent="end">
          <Button color="red" onClick={onDismissStart} text="Create" />
        </Flex>
      )}
    </OverlayPanel.DismissingElement>
  );

  return (
    <Fragment>
      <Box padding={8}>
        <Button
          onClick={() => setShowComponent(true)}
          text="View example OverlayPanel"
        />
      </Box>
      {showComponent && (
        <Layer zIndex={sheetZIndex}>
          <OverlayPanel
            accessibilityDismissButtonLabel="Close audience creation overlay panel"
            accessibilityLabel="Audience list creation for new campaign"
            footer={footer}
            heading="Create a new audience list"
            onDismiss={() => setShowComponent(false)}
            size="md"
          >
            <Flex
              direction="column"
              gap={{
                row: 0,
                column: 12,
              }}
            >
              <Flex
                direction="column"
                gap={{
                  row: 0,
                  column: 4,
                }}
              >
                <Box>
                  <Text inline weight="bold">
                    Step 1:
                  </Text>
                  <Text inline> Audience list details</Text>
                </Box>
                <TextField
                  id="audience-name"
                  label="Audience name"
                  onChange={() => {}}
                  placeholder="Name your audience"
                />
                <TextField
                  id="desc"
                  label="Audience description"
                  onChange={() => {}}
                  placeholder="Describe your audience"
                />
                <Fieldset legend="When adding this audience list to an ad group:">
                  <Flex
                    direction="column"
                    gap={{
                      row: 0,
                      column: 3,
                    }}
                  >
                    <RadioButton
                      id="include"
                      label="Include list"
                      name="audience"
                      onChange={() => {}}
                      value="include"
                    />
                    <RadioButton
                      id="exclude"
                      label="Exclude list"
                      name="audience"
                      onChange={() => {}}
                      value="include"
                    />
                  </Flex>
                </Fieldset>
              </Flex>
              <Flex
                direction="column"
                gap={{
                  row: 0,
                  column: 4,
                }}
              >
                <Box>
                  <Text inline weight="bold">
                    Step 2:
                  </Text>
                  <Text inline> Select conversion source</Text>
                </Box>
                <Text>
                  To use a conversion source other than a Pinterest Tag, add a
                  filter and configure the source of this event.
                </Text>
                <Fieldset
                  legend="Select conversion source:"
                  legendDisplay="hidden"
                >
                  <Flex
                    direction="column"
                    gap={{
                      row: 0,
                      column: 3,
                    }}
                  >
                    <RadioButton
                      id="tag"
                      label="Pinterest Tag"
                      name="source"
                      onChange={() => {}}
                      value="pin"
                    />
                    <RadioButton
                      id="mmp"
                      label="Mobile Measurement Partners (MMP)"
                      name="source"
                      onChange={() => {}}
                      value="mmp"
                    />
                    <RadioButton
                      id="upload"
                      label="Conversion Upload"
                      name="source"
                      onChange={() => {}}
                      value="conversion"
                    />
                    <RadioButton
                      id="api"
                      label="API"
                      name="source"
                      onChange={() => {}}
                      value="api"
                    />
                  </Flex>
                </Fieldset>
              </Flex>
              <Flex
                direction="column"
                gap={{
                  row: 0,
                  column: 4,
                }}
              >
                <Box>
                  <Text inline weight="bold">
                    Step 3:
                  </Text>
                  <Text inline> Set a filter</Text>
                </Box>
                <TextField
                  id="users"
                  label="Users in the past few days"
                  onChange={() => {}}
                  placeholder="Ex. 4"
                />
                <Checkbox
                  id="traffic"
                  label="Include past traffic data"
                  name="traffic"
                  onChange={() => {}}
                />
              </Flex>
            </Flex>
          </OverlayPanel>
        </Layer>
      )}
    </Fragment>
  );
}

Focus management

When OverlayPanel opens, focus should be placed on the first interactive element within the OverlayPanel. When OverlayPanel is closed, focus should be placed back on the button that triggered the OverlayPanel.

Localization

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

Note that accessibilityDismissButtonLabel, dismissConfirmation.message, dismissConfirmation.subtext, dismissConfirmation.primaryAction.accessibilityLabel,
dismissConfirmation.primaryAction.text, dismissConfirmation.secondaryAction.accessibilityLabel,
dismissConfirmation.secondaryAction.text are optional as DefaultLabelProvider provides default strings. Use custom labels if they need to be more specific.

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

export default function Example() {
  const [showComponent, setShowComponent] = useState(true);

  const HEADER_ZINDEX = new FixedZIndex(10);
  const sheetZIndex = new CompositeZIndex([HEADER_ZINDEX]);
  return (
    <DefaultLabelProvider
      labels={{
        OverlayPanel: {
          accessibilityDismissButtonLabel: 'Overlay-Panel auflösen.',
          dismissConfirmationMessage:
            'Sind Sie sicher, dass Sie Ihre Kündigung einreichen wollen?',
          dismissConfirmationSubtext:
            'Sie verlieren alle Ihre Änderungen. Dies kann nicht rückgängig gemacht werden',
          dismissConfirmationPrimaryActionText: 'Ja, entlassen',
          dismissConfirmationPrimaryActionTextLabel:
            'Yes, dismiss the overlay panel',
          dismissConfirmationSecondaryActionText: 'Nein, geh zurück',
          dismissConfirmationSecondaryActionTextLabel:
            'Nein, gehen Sie zurück zum Overlay-Panel',
        },
      }}
    >
      <Box padding={8}>
        <Button
          onClick={() => setShowComponent(true)}
          text="Erstellen Sie eine neue Zielgruppenliste"
        />
      </Box>
      {showComponent && (
        <Layer zIndex={sheetZIndex}>
          <OverlayPanel
            accessibilityDismissButtonLabel="Schließen Sie das Overlay-Panel für die Erstellung der Zielgruppe"
            accessibilityLabel="Erstellung von Zielgruppenlisten für neue Kampagnen"
            dismissConfirmation={{}}
            footer={
              <OverlayPanel.DismissingElement>
                {({ onDismissStart }) => (
                  <Flex alignItems="center" justifyContent="end">
                    <Button
                      color="red"
                      onClick={onDismissStart}
                      text="Erstellen"
                    />
                  </Flex>
                )}
              </OverlayPanel.DismissingElement>
            }
            heading="Erstellen Sie eine neue Zielgruppenliste"
            onDismiss={() => setShowComponent(false)}
            size="md"
          >
            <Box
              alignItems="center"
              display="flex"
              height="100%"
              justifyContent="center"
              width="100%"
            >
              <Text align="center">Inhalt</Text>
            </Box>
          </OverlayPanel>
        </Layer>
      )}
    </DefaultLabelProvider>
  );
}

Subcomponents

DismissingElement

DismissingElement is a render props component that provides access to the callback function onDismissStart. onDismissStart triggers the exit-animation from external trigger points in a component. Internal trigger points are pressing ESC key, built-in dismiss buttons, and clicking outside the component. Use DismissingElement when external elements to the component, such as header, footer or any content element require dismissing the animated component.

DismissingElement Props

DismissingElement subcomponent props
Name
Type
Default
children
Required
(arg1: { onDismissStart: () => void }) => React.Node
-

onDismissStart is passed as render props to any children.

Variants

Heading

As a default, OverlayPanel consists of a heading and content passed as children. The heading of OverlayPanel will have a drop shadow when content scrolls under it.

import { Fragment, useState } from 'react';
import {
  Box,
  Button,
  Checkbox,
  CompositeZIndex,
  Fieldset,
  FixedZIndex,
  Flex,
  Layer,
  OverlayPanel,
  RadioButton,
  Text,
  TextField,
} from 'gestalt';

export default function Example() {
  const [showComponent, setShowComponent] = useState(true);
  const HEADER_ZINDEX = new FixedZIndex(10);
  const sheetZIndex = new CompositeZIndex([HEADER_ZINDEX]);

  const footer = (
    <OverlayPanel.DismissingElement>
      {({ onDismissStart }) => (
        <Flex alignItems="center" justifyContent="end">
          <Button color="red" onClick={onDismissStart} text="Create" />
        </Flex>
      )}
    </OverlayPanel.DismissingElement>
  );

  return (
    <Fragment>
      <Box padding={8}>
        <Button
          onClick={() => setShowComponent(true)}
          text="View example OverlayPanel"
        />
      </Box>
      {showComponent && (
        <Layer zIndex={sheetZIndex}>
          <OverlayPanel
            accessibilityDismissButtonLabel="Close audience creation overlay panel"
            accessibilityLabel="Audience list creation for new campaign"
            footer={footer}
            heading="Create a new audience list"
            onDismiss={() => setShowComponent(false)}
            size="md"
          >
            <Flex
              direction="column"
              gap={{
                row: 0,
                column: 12,
              }}
            >
              <Flex
                direction="column"
                gap={{
                  row: 0,
                  column: 4,
                }}
              >
                <Box>
                  <Text inline weight="bold">
                    Step 1:
                  </Text>
                  <Text inline> Audience list details</Text>
                </Box>
                <TextField
                  id="audience-name"
                  label="Audience name"
                  onChange={() => {}}
                  placeholder="Name your audience"
                />
                <TextField
                  id="desc"
                  label="Audience description"
                  onChange={() => {}}
                  placeholder="Describe your audience"
                />
                <Fieldset legend="When adding this audience list to an ad group:">
                  <Flex
                    direction="column"
                    gap={{
                      row: 0,
                      column: 3,
                    }}
                  >
                    <RadioButton
                      id="include"
                      label="Include list"
                      name="audience"
                      onChange={() => {}}
                      value="include"
                    />
                    <RadioButton
                      id="exclude"
                      label="Exclude list"
                      name="audience"
                      onChange={() => {}}
                      value="include"
                    />
                  </Flex>
                </Fieldset>
              </Flex>
              <Flex
                direction="column"
                gap={{
                  row: 0,
                  column: 4,
                }}
              >
                <Box>
                  <Text inline weight="bold">
                    Step 2:
                  </Text>
                  <Text inline> Select conversion source</Text>
                </Box>
                <Text>
                  To use a conversion source other than a Pinterest Tag, add a
                  filter and configure the source of this event.
                </Text>
                <Fieldset
                  legend="Select conversion source:"
                  legendDisplay="hidden"
                >
                  <Flex
                    direction="column"
                    gap={{
                      row: 0,
                      column: 3,
                    }}
                  >
                    <RadioButton
                      id="tag"
                      label="Pinterest Tag"
                      name="source"
                      onChange={() => {}}
                      value="pin"
                    />
                    <RadioButton
                      id="mmp"
                      label="Mobile Measurement Partners (MMP)"
                      name="source"
                      onChange={() => {}}
                      value="mmp"
                    />
                    <RadioButton
                      id="upload"
                      label="Conversion Upload"
                      name="source"
                      onChange={() => {}}
                      value="conversion"
                    />
                    <RadioButton
                      id="api"
                      label="API"
                      name="source"
                      onChange={() => {}}
                      value="api"
                    />
                  </Flex>
                </Fieldset>
              </Flex>
              <Flex
                direction="column"
                gap={{
                  row: 0,
                  column: 4,
                }}
              >
                <Box>
                  <Text inline weight="bold">
                    Step 3:
                  </Text>
                  <Text inline> Set a filter</Text>
                </Box>
                <TextField
                  id="users"
                  label="Users in the past few days"
                  onChange={() => {}}
                  placeholder="Ex. 4"
                />
                <Checkbox
                  id="traffic"
                  label="Include past traffic data"
                  name="traffic"
                  onChange={() => {}}
                />
              </Flex>
            </Flex>
          </OverlayPanel>
        </Layer>
      )}
    </Fragment>
  );
}

Sub-heading

A subHeading is a container that can be used for additional navigation or sub-text. The sub-heading sits at the top under the heading, and will always remain visible if the content scrolls.

function _optionalChain(ops) {
  let lastAccessLHS = undefined;
  let value = ops[0];
  let i = 1;
  while (i < ops.length) {
    const op = ops[i];
    const fn = ops[i + 1];
    i += 2;
    if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) {
      return undefined;
    }
    if (op === 'access' || op === 'optionalAccess') {
      lastAccessLHS = value;
      value = fn(value);
    } else if (op === 'call' || op === 'optionalCall') {
      value = fn((...args) => value.call(lastAccessLHS, ...args));
      lastAccessLHS = undefined;
    }
  }
  return value;
}
import { Fragment, useRef, useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  FixedZIndex,
  Flex,
  Layer,
  List,
  OverlayPanel,
  Tabs,
  Text,
} from 'gestalt';

export default function SubheadingExample() {
  const [showComponent, setShowComponent] = useState(true);
  const HEADER_ZINDEX = new FixedZIndex(10);
  const sheetZIndex = new CompositeZIndex([HEADER_ZINDEX]);

  const [activeTabIndex, setActiveTabIndex] = useState(0);
  const enRef = useRef(null);
  const esRef = useRef(null);
  const ptRef = useRef(null);
  const chRef = useRef(null);
  const refs = [enRef, esRef, ptRef, chRef];

  const handleChangeTab = ({ activeTabIndex: activeTabIndexLocal, event }) => {
    event.preventDefault();
    setActiveTabIndex(activeTabIndexLocal);
    _optionalChain([
      refs,
      'access',
      (_) => _[activeTabIndexLocal],
      'optionalAccess',
      (_2) => _2.current,
      'optionalAccess',
      (_3) => _3.scrollIntoView,
      'call',
      (_4) =>
        _4({
          behavior: 'smooth',
        }),
    ]);
  };

  return (
    <Fragment>
      <Box padding={8}>
        <Button
          onClick={() => setShowComponent(true)}
          text="View subheading example"
        />
      </Box>
      {showComponent && (
        <Layer zIndex={sheetZIndex}>
          <OverlayPanel
            accessibilityDismissButtonLabel="Close"
            accessibilityLabel="Example overlay panel to demonstrate subHeading"
            footer={
              <Flex justifyContent="end">
                <Button color="red" text="Apply changes" />
              </Flex>
            }
            heading="OverlayPanel with subHeading"
            onDismiss={() => setShowComponent(false)}
            size="md"
            subHeading={
              <Box marginBottom={4} marginEnd={8} marginStart={8}>
                <Tabs
                  activeTabIndex={activeTabIndex}
                  onChange={handleChangeTab}
                  tabs={[
                    {
                      text: 'English',
                      href: '#',
                    },
                    {
                      text: 'Español',
                      href: '#',
                    },
                    {
                      text: 'Português',
                      href: '#',
                    },
                    {
                      text: '普通话',
                      href: '#',
                    },
                  ]}
                />
              </Box>
            }
          >
            <Flex direction="column" gap={2}>
              <Box ref={enRef}>
                <List label={<Text weight="bold">English</Text>}>
                  <List.Item text="One" />
                  <List.Item text="Two" />
                  <List.Item text="Three" />
                  <List.Item text="Four" />
                  <List.Item text="Five" />
                  <List.Item text="Six" />
                  <List.Item text="Seven" />
                  <List.Item text="Eight" />
                  <List.Item text="Nine" />
                  <List.Item text="Ten" />
                </List>
              </Box>

              <Box ref={esRef}>
                <List label={<Text weight="bold">Español</Text>}>
                  <List.Item text="Dos" />
                  <List.Item text="Tres" />
                  <List.Item text="Cuatro" />
                  <List.Item text="Cinco" />
                  <List.Item text="Seis" />
                  <List.Item text="Siete" />
                  <List.Item text="Ocho" />
                  <List.Item text="Nueve" />
                  <List.Item text="Diez" />
                </List>
              </Box>

              <Box ref={ptRef}>
                <List label={<Text weight="bold">Português</Text>}>
                  <List.Item text="Um" />
                  <List.Item text="Dois" />
                  <List.Item text="Três" />
                  <List.Item text="Quatro" />
                  <List.Item text="Cinco" />
                  <List.Item text="Seis" />
                  <List.Item text="Sete" />
                  <List.Item text="Oito" />
                  <List.Item text="Nove" />
                  <List.Item text="Dez" />
                </List>
              </Box>

              <Box ref={chRef}>
                <List label={<Text weight="bold">普通话</Text>}>
                  <List.Item text="一" />
                  <List.Item text="二" />
                  <List.Item text="三" />
                  <List.Item text="四" />
                  <List.Item text="五" />
                  <List.Item text="六" />
                  <List.Item text="七" />
                  <List.Item text="八" />
                  <List.Item text="九" />
                  <List.Item text="十" />
                </List>
              </Box>
            </Flex>
          </OverlayPanel>
        </Layer>
      )}
    </Fragment>
  );
}

The footer is used for OverlayPanel tasks that require additional actions, such as submitting or deleting information.

import { Fragment, useState } from 'react';
import {
  Accordion,
  Box,
  Button,
  CompositeZIndex,
  Fieldset,
  FixedZIndex,
  Flex,
  Layer,
  OverlayPanel,
  RadioButton,
  Text,
} from 'gestalt';

export default function Example() {
  const [showComponent, setShowComponent] = useState(true);
  const HEADER_ZINDEX = new FixedZIndex(10);
  const sheetZIndex = new CompositeZIndex([HEADER_ZINDEX]);

  const footer = (
    <OverlayPanel.DismissingElement>
      {({ onDismissStart }) => (
        <Flex alignItems="center" justifyContent="between">
          <Button color="transparent" text="Delete" />
          <Button color="red" onClick={onDismissStart} text="Apply changes" />
        </Flex>
      )}
    </OverlayPanel.DismissingElement>
  );

  return (
    <Fragment>
      <Box padding={8}>
        <Button
          onClick={() => setShowComponent(true)}
          text="View footer example"
        />
      </Box>
      {showComponent && (
        <Layer zIndex={sheetZIndex}>
          <OverlayPanel
            accessibilityDismissButtonLabel="Close"
            accessibilityLabel="Bulk edit for 5 ad groups of Nordstrom Account"
            footer={footer}
            heading="Editing 5 ad groups"
            onDismiss={() => setShowComponent(false)}
            size="md"
          >
            <Flex
              direction="column"
              gap={{
                row: 0,
                column: 8,
              }}
            >
              <Text weight="bold">Bids</Text>
              <Flex
                gap={{
                  row: 4,
                  column: 0,
                }}
              >
                <Text>
                  Adjust bids for the selected ad groups below. Changes made
                  here will apply to all selected ad groups.
                </Text>
                <Flex.Item flex="none">
                  <Button disabled text="Reset bids" />
                </Flex.Item>
              </Flex>
              <Accordion.Expandable
                expandedIndex={0}
                id="AccordionExample - default"
                items={[
                  {
                    children: (
                      <Fieldset
                        legend="What bid campaign do you want to run?"
                        legendDisplay="hidden"
                      >
                        <Flex
                          direction="column"
                          gap={{
                            row: 0,
                            column: 2,
                          }}
                        >
                          <RadioButton
                            checked
                            id="favoriteDog"
                            label="No change"
                            name="favorite"
                            onChange={() => {}}
                            value="dogs"
                          />
                          <RadioButton
                            checked={false}
                            id="favoriteCat"
                            label="Automatic (recommended)"
                            name="favorite"
                            onChange={() => {}}
                            subtext="Pinterest aims to get the most clicks for your budget"
                            value="cats"
                          />
                          <RadioButton
                            checked={false}
                            id="favoritePlants"
                            label="Custom"
                            name="favorite"
                            onChange={() => {}}
                            subtext="You control how much to bid at auctions"
                            value="plants"
                          />
                        </Flex>
                      </Fieldset>
                    ),
                    summary: ['Custom'],
                    title: 'Bid',
                  },
                ]}
              />

              <Accordion.Expandable
                id="accordionExample - preview"
                items={[
                  {
                    children: <Text> Preview table of changes here</Text>,
                    summary: ['5 ad groups changing'],
                    title: 'Preview bid changes',
                  },
                ]}
              />
            </Flex>
          </OverlayPanel>
        </Layer>
      )}
    </Fragment>
  );
}

Sizes

OverlayPanel comes in 3 sizes: small (sm), medium (md), and large (lg).

  • Small OverlayPanels (540px) are primarily used for displaying information or acting as a point to link to other content. They are the least commonly used.
  • Medium OverlayPanels (720px) are the standard size offered for content.
  • Large OverlayPanels (900px) should be used in cases where there may be columns of content or navigation where the additional space is required to keep the content at a comfortable reading width.
import { Fragment, useReducer } from 'react';
import {
  Accordion,
  Box,
  Button,
  CompositeZIndex,
  Fieldset,
  FixedZIndex,
  Flex,
  Layer,
  OverlayPanel,
  RadioButton,
  Text,
} from 'gestalt';

export default function Example() {
  function reducer(
    state,

    action
  ) {
    switch (action.type) {
      case 'small':
        return { heading: 'Small overlay panel', size: 'sm' };
      case 'medium':
        return { heading: 'Medium overlay panel', size: 'md' };
      case 'large':
        return { heading: 'Large overlay panel', size: 'lg' };
      case 'none':
        return {};
      default:
        throw new Error();
    }
  }
  const initialState = Object.freeze({});
  const [state, dispatch] = useReducer(reducer, initialState);
  const HEADER_ZINDEX = new FixedZIndex(10);
  const sheetZIndex = new CompositeZIndex([HEADER_ZINDEX]);

  return (
    <Fragment>
      <Box padding={8}>
        <Box padding={1}>
          <Button
            onClick={() => {
              dispatch({ type: 'small' });
            }}
            text="Small OverlayPanel"
          />
        </Box>
        <Box padding={1}>
          <Button
            onClick={() => {
              dispatch({ type: 'medium' });
            }}
            text="Medium OverlayPanel"
          />
        </Box>
        <Box padding={1}>
          <Button
            onClick={() => {
              dispatch({ type: 'large' });
            }}
            text="Large OverlayPanel"
          />
        </Box>
      </Box>
      {state.size && (
        <Layer zIndex={sheetZIndex}>
          <OverlayPanel
            accessibilityDismissButtonLabel="Dismiss"
            accessibilityLabel="Example overlay panel to demonstrate different sizes"
            footer={
              <Flex justifyContent="end">
                <Button color="red" text="Apply changes" />
              </Flex>
            }
            heading={state.heading}
            onDismiss={() => {
              dispatch({ type: 'none' });
            }}
            size={state.size}
          >
            <Flex
              direction="column"
              gap={{
                row: 0,
                column: 8,
              }}
            >
              <Text weight="bold">Bids</Text>
              <Flex
                gap={{
                  row: 4,
                  column: 0,
                }}
              >
                <Text>
                  Adjust bids for the selected ad groups below. Changes made
                  here will apply to all selected ad groups.
                </Text>
                <Flex.Item flex="none">
                  <Button disabled text="Reset bids" />
                </Flex.Item>
              </Flex>
              <Accordion.Expandable
                expandedIndex={0}
                id="accordionExample - default"
                items={[
                  {
                    children: (
                      <Fieldset
                        legend="What bid campaign do you want to run?"
                        legendDisplay="hidden"
                      >
                        <Flex
                          direction="column"
                          gap={{
                            row: 0,
                            column: 2,
                          }}
                        >
                          <RadioButton
                            checked
                            id="favoriteDog"
                            label="No change"
                            name="favorite"
                            onChange={() => {}}
                            value="dogs"
                          />
                          <RadioButton
                            checked={false}
                            id="favoriteCat"
                            label="Automatic (recommended)"
                            name="favorite"
                            onChange={() => {}}
                            subtext="Pinterest aims to get the most clicks for your budget"
                            value="cats"
                          />
                          <RadioButton
                            checked={false}
                            id="favoritePlants"
                            label="Custom"
                            name="favorite"
                            onChange={() => {}}
                            subtext="You control how much to bid at auctions"
                            value="plants"
                          />
                        </Flex>
                      </Fieldset>
                    ),
                    summary: ['Custom'],
                    title: 'Bid',
                  },
                ]}
              />
              <Accordion.Expandable
                id="ModuleExample - preview"
                items={[
                  {
                    children: <Text> Preview table of changes here</Text>,
                    summary: ['5 ad groups changing'],
                    title: 'Preview bid changes',
                  },
                ]}
              />
            </Flex>
          </OverlayPanel>
        </Layer>
      )}
    </Fragment>
  );
}

Preventing close on outside click

By default, users can click outside OverlayPanel (on the overlay) to close it. This can be disabled by setting closeOnOutsideClick to false. This may be implemented in order to prevent users from accidentally clicking out of the OverlayPanel and losing information they’ve entered. The ESC key can still be used to close the OverlayPanel.

import { Fragment, useState } from 'react';
import {
  Box,
  Button,
  Checkbox,
  CompositeZIndex,
  Fieldset,
  FixedZIndex,
  Flex,
  Layer,
  OverlayPanel,
  RadioButton,
  Text,
  TextField,
} from 'gestalt';

export default function Example() {
  const [showComponent, setShowComponent] = useState(true);
  const HEADER_ZINDEX = new FixedZIndex(10);
  const sheetZIndex = new CompositeZIndex([HEADER_ZINDEX]);

  const footer = (
    <OverlayPanel.DismissingElement>
      {({ onDismissStart }) => (
        <Flex alignItems="center" justifyContent="end">
          <Button color="red" onClick={onDismissStart} text="Create" />
        </Flex>
      )}
    </OverlayPanel.DismissingElement>
  );

  return (
    <Fragment>
      <Box padding={8}>
        <Button
          onClick={() => setShowComponent(true)}
          text="View OverlayPanel"
        />
      </Box>
      {showComponent && (
        <Layer zIndex={sheetZIndex}>
          <OverlayPanel
            accessibilityDismissButtonLabel="Close"
            accessibilityLabel="Example overlay panel for demonstration"
            closeOnOutsideClick={false}
            footer={footer}
            heading="Create new audience list"
            onDismiss={() => setShowComponent(false)}
            size="md"
          >
            <Flex
              direction="column"
              gap={{
                row: 0,
                column: 12,
              }}
            >
              <Flex
                direction="column"
                gap={{
                  row: 0,
                  column: 4,
                }}
              >
                <Box>
                  <Text inline weight="bold">
                    Step 1:
                  </Text>
                  <Text inline> Audience list details</Text>
                </Box>
                <TextField
                  id="name-your-audience"
                  label="Audience name"
                  onChange={() => {}}
                  placeholder="Name your audience"
                />
                <TextField
                  id="describe-your-audience"
                  label="Audience description"
                  onChange={() => {}}
                  placeholder="Describe your audience"
                />
                <Fieldset legend="When adding this audience list to an ad group:">
                  <Flex
                    direction="column"
                    gap={{
                      row: 0,
                      column: 3,
                    }}
                  >
                    <RadioButton
                      id="include-list"
                      label="Include list"
                      name="audience"
                      onChange={() => {}}
                      value="include"
                    />
                    <RadioButton
                      id="exclude-list"
                      label="Exclude list"
                      name="audience"
                      onChange={() => {}}
                      value="include"
                    />
                  </Flex>
                </Fieldset>
              </Flex>
              <Flex
                direction="column"
                gap={{
                  row: 0,
                  column: 4,
                }}
              >
                <Box>
                  <Text inline weight="bold">
                    Step 2:
                  </Text>
                  <Text inline> Select conversion source</Text>
                </Box>
                <Text>
                  To use a conversion source other than a Pinterest Tag, add a
                  filter and configure the source of this event.
                </Text>
                <Fieldset
                  legend="Select conversion source:"
                  legendDisplay="hidden"
                >
                  <Flex
                    direction="column"
                    gap={{
                      row: 0,
                      column: 3,
                    }}
                  >
                    <RadioButton
                      id="pinterest-tag"
                      label="Pinterest Tag"
                      name="source"
                      onChange={() => {}}
                      value="pin"
                    />
                    <RadioButton
                      id="mobile-measurement"
                      label="Mobile Measurement Partners (MMP)"
                      name="source"
                      onChange={() => {}}
                      value="mmp"
                    />
                    <RadioButton
                      id="conversion-upload"
                      label="Conversion Upload"
                      name="source"
                      onChange={() => {}}
                      value="conversion"
                    />
                    <RadioButton
                      id="api"
                      label="API"
                      name="source"
                      onChange={() => {}}
                      value="api"
                    />
                  </Flex>
                </Fieldset>
              </Flex>
              <Flex
                direction="column"
                gap={{
                  row: 0,
                  column: 4,
                }}
              >
                <Box>
                  <Text inline weight="bold">
                    Step 3:
                  </Text>
                  <Text inline> Set a filter</Text>
                </Box>
                <TextField
                  id="past-users"
                  label="Users in the past few days"
                  onChange={() => {}}
                  placeholder="Ex. 4"
                />
                <Checkbox
                  id="traffic"
                  label="Include past traffic data"
                  name="traffic"
                  onChange={() => {}}
                />
              </Flex>
            </Flex>
          </OverlayPanel>
        </Layer>
      )}
    </Fragment>
  );
}

Animation

By default, OverlayPanel animates in, with the initial render process from the entry-point, and out, when the ESC key is pressed, the header close button is pressed, or the user clicks outside of the OverlayPanel. However, to trigger the exit-animation from other elements in other areas such as the children or footer, the following render prop can be used:

<OverlayPanel.DismissingElement>
  ({ onDismissStart }) => ( ... )
</OverlayPanel.DismissingElement>

When using this render prop, just pass the argument onDismissStart to your exit-point action elements. In the example below, we've added the exit animation to the:

  • Close button (subHeading)
  • Right arrow icon red button (children)
  • Done red button (children)
  • Left arrow red icon button (children)
  • Close button (footer)

OverlayPanel also provides onAnimationEnd, a callback that gets triggered at the end of each animation. The callback has access to animationState to identify the end of each 'in' and 'out' animation for cases where the two events trigger different responses.

Don't use OverlayPanel's onDismissStart render prop available in subheading, footer and children; they will be deprecated and removed soon. Instead, wrap the component dismissing your OverlayPanel with OverlayPanel.DismissingElement and access the onDismissStart render prop available there.
import { Fragment, useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  FixedZIndex,
  Flex,
  IconButton,
  Layer,
  OverlayPanel,
} from 'gestalt';

export default function Example() {
  const [showComponent, setShowComponent] = useState(true);

  const HEADER_ZINDEX = new FixedZIndex(10);
  const sheetZIndex = new CompositeZIndex([HEADER_ZINDEX]);

  const renderSubheading = (
    <OverlayPanel.DismissingElement>
      {({ onDismissStart }) => (
        <Box marginBottom={4} marginEnd={8} marginStart={8}>
          <Button
            color="blue"
            onClick={onDismissStart}
            text="Close on Sub-heading"
          />
        </Box>
      )}
    </OverlayPanel.DismissingElement>
  );

  const renderFooter = (
    <OverlayPanel.DismissingElement>
      {({ onDismissStart }) => (
        <Flex justifyContent="end">
          <Button onClick={onDismissStart} text="Close on Footer" />
        </Flex>
      )}
    </OverlayPanel.DismissingElement>
  );

  const renderContent = (
    <OverlayPanel.DismissingElement>
      {({ onDismissStart }) => (
        <Flex alignItems="center" height="100%" justifyContent="center">
          <IconButton
            accessibilityLabel="Done icon left"
            icon="directional-arrow-right"
            iconColor="red"
            onClick={onDismissStart}
            size="lg"
          />
          <Button
            color="red"
            onClick={onDismissStart}
            size="lg"
            text="Done on Children"
          />
          <IconButton
            accessibilityLabel="Done icon right"
            icon="directional-arrow-left"
            iconColor="red"
            onClick={onDismissStart}
            size="lg"
          />
        </Flex>
      )}
    </OverlayPanel.DismissingElement>
  );

  return (
    <Fragment>
      <Box padding={8}>
        <Button
          onClick={() => setShowComponent(true)}
          text="Open example overlay panel"
        />
      </Box>
      {showComponent && (
        <Layer zIndex={sheetZIndex}>
          <OverlayPanel
            accessibilityDismissButtonLabel="Close"
            accessibilityLabel="Animated overlay panel"
            footer={renderFooter}
            heading="Animated OverlayPanel"
            onDismiss={() => setShowComponent(false)}
            size="md"
            subHeading={renderSubheading}
          >
            {renderContent}
          </OverlayPanel>
        </Layer>
      )}
    </Fragment>
  );
}

Dismiss confirmation

There are two ways OverlayPanel can be dismissed: internally-controlled and externally-controlled dismiss actions.

The three internally-controlled or component-controlled dismiss actions are:

  • when the ESC key is pressed
  • when the backdrop is clicked
  • when the dismiss IconButton is clicked

The externally-controlled dismiss actions (subHeading, children, and footer) require implementing the callback onDismissStart. See the animation variant to learn more.

OverlayPanels can contain forms or be part of flows where the user is required to submit infomation. If an OverlayPanel is dismissed involuntarily, the data entered by the user could not be saved and lost. This can create a bad user experience.

To prevent dismissing OverlayPanel involuntary, we can use dismissConfirmation. When provided, it will open a confirmation modal each time component-controlled dismiss actions are triggered.

import { Fragment, useState } from 'react';
import {
  Box,
  Button,
  Checkbox,
  CompositeZIndex,
  Fieldset,
  FixedZIndex,
  Flex,
  Layer,
  OverlayPanel,
  RadioButton,
  Text,
  TextField,
} from 'gestalt';

export default function Example() {
  const [showComponent, setShowComponent] = useState(true);

  const HEADER_ZINDEX = new FixedZIndex(10);
  const sheetZIndex = new CompositeZIndex([HEADER_ZINDEX]);

  return (
    <Fragment>
      <Box padding={8}>
        <Button
          onClick={() => setShowComponent(true)}
          text="View example OverlayPanel"
        />
      </Box>
      {showComponent && (
        <Layer zIndex={sheetZIndex}>
          <OverlayPanel
            accessibilityDismissButtonLabel="Close audience creation overlaypanel"
            accessibilityLabel="Audience list creation for new campaign"
            dismissConfirmation={{}}
            footer={
              <OverlayPanel.DismissingElement>
                {({ onDismissStart }) => (
                  <Flex alignItems="center" justifyContent="end">
                    <Button
                      color="red"
                      onClick={onDismissStart}
                      text="Create"
                    />
                  </Flex>
                )}
              </OverlayPanel.DismissingElement>
            }
            heading="Create a new audience list"
            onDismiss={() => setShowComponent(false)}
            size="md"
          >
            <Flex
              direction="column"
              gap={{
                row: 0,
                column: 12,
              }}
            >
              <Flex
                direction="column"
                gap={{
                  row: 0,
                  column: 4,
                }}
              >
                <Box>
                  <Text inline weight="bold">
                    Step 1:
                  </Text>
                  <Text inline> Audience list details</Text>
                </Box>
                <TextField
                  id="audience-name"
                  label="Audience name"
                  onChange={() => {}}
                  placeholder="Name your audience"
                />
                <TextField
                  id="desc"
                  label="Audience description"
                  onChange={() => {}}
                  placeholder="Describe your audience"
                />
                <Fieldset legend="When adding this audience list to an ad group:">
                  <Flex
                    direction="column"
                    gap={{
                      row: 0,
                      column: 3,
                    }}
                  >
                    <RadioButton
                      id="include"
                      label="Include list"
                      name="audience"
                      onChange={() => {}}
                      value="include"
                    />
                    <RadioButton
                      id="exclude"
                      label="Exclude list"
                      name="audience"
                      onChange={() => {}}
                      value="include"
                    />
                  </Flex>
                </Fieldset>
              </Flex>
              <Flex
                direction="column"
                gap={{
                  row: 0,
                  column: 4,
                }}
              >
                <Box>
                  <Text inline weight="bold">
                    Step 2:
                  </Text>
                  <Text inline> Select conversion source</Text>
                </Box>
                <Text>
                  To use a conversion source other than a Pinterest Tag, add a
                  filter and configure the source of this event.
                </Text>
                <Fieldset
                  legend="Select conversion source:"
                  legendDisplay="hidden"
                >
                  <Flex
                    direction="column"
                    gap={{
                      row: 0,
                      column: 3,
                    }}
                  >
                    <RadioButton
                      id="tag"
                      label="Pinterest Tag"
                      name="source"
                      onChange={() => {}}
                      value="pin"
                    />
                    <RadioButton
                      id="mmp"
                      label="Mobile Measurement Partners (MMP)"
                      name="source"
                      onChange={() => {}}
                      value="mmp"
                    />
                    <RadioButton
                      id="upload"
                      label="Conversion Upload"
                      name="source"
                      onChange={() => {}}
                      value="conversion"
                    />
                    <RadioButton
                      id="api"
                      label="API"
                      name="source"
                      onChange={() => {}}
                      value="api"
                    />
                  </Flex>
                </Fieldset>
              </Flex>
              <Flex
                direction="column"
                gap={{
                  row: 0,
                  column: 4,
                }}
              >
                <Box>
                  <Text inline weight="bold">
                    Step 3:
                  </Text>
                  <Text inline>Set a filter</Text>
                </Box>
                <TextField
                  id="users"
                  label="Users in the past few days"
                  onChange={() => {}}
                  placeholder="Ex. 4"
                />
                <Checkbox
                  id="traffic"
                  label="Include past traffic data"
                  name="traffic"
                  onChange={() => {}}
                />
              </Flex>
            </Flex>
          </OverlayPanel>
        </Layer>
      )}
    </Fragment>
  );
}

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.

Modal
For alerts, actions or acknowledgments that should block the user’s current flow, use Modal.

Toast
Toast provides feedback on an interaction. 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.