SheetMobile is a mobile only component. It is not used in desktop experiences.

SheetMobile is a supplementary container that sits on top of the screen’s primary content and can be dismissed in order to interact with the underlying content. Sheets can contain a wide variety of information and layouts, including menu items, actions, and supplemental content.

also known as Panel, Dialog, Drawer, Tray

SheetMobile is in pilot phase. Expect development and design iteration and breaking API changes as well as further documentation development.
Figma:

Responsive:

Adaptive:

Props

Component props
Name
Type
Default
heading
Required
string
-

The text used for SheetMobile's heading. See the header variant for more info.

onDismiss
Required
() => void
-

Callback fired when SheetMobile is dismissed. Must be used for controlling SheetMobile's visibility state.

align
"start" | "center"
"start"

Specify the alignment of heading & subHeading strings. See the Header variant for more info.

backIconButton
{
  accessibilityLabel: string;
  onClick: (arg1: {
    event:
      | React.MouseEvent<HTMLButtonElement>
      | React.KeyboardEvent<HTMLButtonElement>
      | React.MouseEvent<HTMLAnchorElement>
      | React.KeyboardEvent<HTMLAnchorElement>;
    onDismissStart: () => void;
  }) => void;
}
-

Adds a "back-arrow" IconButton for user interaction at the start of the header section. See the header variant, back and forward navigation case for more info.

children
React.Node
-

Supply the element(s) that will be used as SheetMobile's main content.

closeOnOutsideClick
boolean
true

Indicate whether clicking on the backdrop (gray area) outside of SheetMobile will dismiss it or not. See the Preventing close on outside click variant for more info.

React.Node
-

Supply the element(s) that will be used as SheetMobile's custom footer. See the footer variant for more info.

forwardIconButton
{
  accessibilityLabel: string;
  onClick: (arg1: {
    event:
      | React.MouseEvent<HTMLButtonElement>
      | React.KeyboardEvent<HTMLButtonElement>
      | React.MouseEvent<HTMLAnchorElement>
      | React.KeyboardEvent<HTMLAnchorElement>;
    onDismissStart: () => void;
  }) => void;
}
-

Adds a "forward-arrow" IconButton for user interaction at the end of the header section.. See the header variant, back and forward navigation case for more info.

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

Callback fired when SheetMobile's in & out animations end. See the animation variant to learn more.

onOutsideClick
(arg1: { event: React.MouseEvent<HTMLDivElement> }) => void
-

Callback fired when clicking on the backdrop (gray area) outside of SheetMobile.

padding
"default" | "none"
-

The main SheetMobile content section has a "default" padding. For those cases where full bleed is needed, set padding to "none".

primaryAction
{
  accessibilityLabel: string;
  href?: string;
  label: string;
  onClick: (arg1: {
    event:
      | React.MouseEvent<HTMLButtonElement>
      | React.KeyboardEvent<HTMLButtonElement>
      | React.MouseEvent<HTMLAnchorElement>
      | React.KeyboardEvent<HTMLAnchorElement>;
    onDismissStart: () => void;
  }) => void;
  rel?: ComponentProps<typeof Link>["rel"];
  size?: ComponentProps<typeof Button>["size"];
  target?: ComponentProps<typeof Link>["target"];
}
-

Adds an primary action Button for user interaction at the end of the header section. See the header variant, with primary action case for more info.

role
"alertdialog" | "dialog"
"dialog"

The underlying ARIA role for the SheetMobile. See the Accessibility Role section for more info.

showDismissButton
boolean
true

Shows a dismiss button on SheetMobile. See the header variant, dismiss button case for more info.

size
"default" | "full" | "auto"
"default"

Sets the SheetMobile's height. See the size variant for more info.

subHeading
string
-

Subheading for SheetMobile. See the header variant for more info.

zIndex
Indexable
-

An object representing the zIndex value of SheetMobile. Learn more about zIndex classes

Usage guidelines

When to use
  • Use a partial sheet when the content of sheet compliments the content of the primary screen behind.
  • Use a full sheet when the content of the sheet does not need to reference the primary screen behind. Usually serving as a lightweight way to complete actions or move through a flow.
When not to use
  • If possible, similar content should remain on a primary screen. Only use SheetMobile if the content is a unique experience to the primary screen and/or to reference its content.
  • To present binary or blocking decisions. Use an ModalAlert or Modal instead.
  • For a temporary message. Use Toast instead.

Best practices

Do
  • Always include a collapse affordance. SheetMobile should close when users press the dismiss icon button, a cancel/close button, when swiped away or when users tap the area outside the partial sheet.
  • Include a grabber for partial sheets. This provides a visual indicator of resizability and allows screen reader users to resize the sheet.
  • Include a header title as it adds context to the task. Headers can be either center or start aligned, but should remain consistant throughout a flow.
Don't
  • Have more than two buttons (primary and secondary) in the footer of the sheet. This prevents unclear hierarchy and crowding on mobile screens. Footers should be simple and provide clear actions for the user.
  • Remove the wash behind the partial SheetMobile. The wash separates the sheet content from the primary content and allows for better focus and accessibility.
  • Display more than one sheet at a time or overlay sheets. For transitions and navigation within sheets, view the Interactions and transitions section below.
  • Truncate header text. Headers should be no more than two lines of text. If they are, consider revising the content.

Accessibility

Localization

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

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

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

  return (
    <DefaultLabelProvider
      labels={{
        SheetMobile: {
          accessibilityDismissButtonLabel: 'Das untere Blatt ist zu verwerfen',
          accessibilityGrabberLabel: 'Greifer',
          accessibilityLabel: 'Unteres Blatt.',
        },
      }}
    >
      <DeviceTypeProvider deviceType="mobile">
        {showComponent ? (
          <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
            <SheetMobile
              heading="Beginnen Sie jetzt mit der Erstellung."
              onDismiss={() => setShowComponent(false)}
              showDismissButton
              subHeading="Die Inspiration beginnt hier."
            >
              <Text>Inhalt</Text>
            </SheetMobile>
          </Layer>
        ) : null}
        <Box padding={2}>
          <Button
            accessibilityLabel="Auf Pinterest erstellen."
            color="red"
            onClick={() => setShowComponent(true)}
            size="lg"
            text="Erstellen."
          />
        </Box>
      </DeviceTypeProvider>
    </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

Size

SheetMobile is used only on mobile web experiences.

Use a partial SheetMobile when the content of the sheet compliments the content of the primary screen behind. Partial SheetMobiles have a 40% wash behind the sheet. This allows users to view content but not interact. Tapping the scrim behind the sheet will dismiss the sheet and the wash by default.

There are two size variants for partial SheetMobiles: "default" and "auto". See examples below for more details.

Use a full SheetMobile when the content of the sheet does not need to reference the primary screen behind. Usually serving as a lightweight way to complete actions or move through a flow.

There is one size variant for full Sheets: "full". See examples below for more details.

Default

A partial SheetMobile with a fix height of 50% of the screen height. When size is set to "default", SheetMobile doesn't require a visible dismiss IconButton.

import { useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  FixedZIndex,
  Flex,
  Layer,
  SheetMobile,
  Text,
} from 'gestalt';

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

  return (
    <DeviceTypeProvider deviceType="mobile">
      {showComponent ? (
        <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
          <SheetMobile
            footer={
              <Flex gap={2} justifyContent="center">
                <Button color="gray" text="Secondary" />
                <Button color="red" text="Primary" />
              </Flex>
            }
            heading="Heading"
            onDismiss={() => setShowComponent(false)}
            primaryAction={{
              accessibilityLabel: 'Next page',
              label: 'Next',
              onClick: () => {},
            }}
          >
            <Box>
              {Array(100).map((number, index) => {
                const key = `example${index}`;
                return <Text key={key}>Content</Text>;
              })}
            </Box>
          </SheetMobile>
        </Layer>
      ) : null}
      <Box padding={2}>
        <Button
          accessibilityLabel="Show SheetMobile"
          color="red"
          onClick={() => setShowComponent(true)}
          size="lg"
          text="Show SheetMobile"
        />
      </Box>
    </DeviceTypeProvider>
  );
}

Auto

A partial SheetMobile with a max of 90% and a min of 30% screen height. When size is set to "auto", SheetMobile doesn't require a visible dismiss IconButton.

import { useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  FixedZIndex,
  Flex,
  Layer,
  SheetMobile,
  Text,
} from 'gestalt';

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

  return (
    <DeviceTypeProvider deviceType="mobile">
      {showComponent ? (
        <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
          <SheetMobile
            footer={
              <Flex gap={2} justifyContent="center">
                <Button color="gray" text="Secondary" />
                <Button color="red" text="Primary" />
              </Flex>
            }
            heading="Heading"
            onDismiss={() => setShowComponent(false)}
            primaryAction={{
              accessibilityLabel: 'Next page',
              label: 'Next',
              onClick: () => {},
            }}
            size="auto"
          >
            <Box>
              {Array(100).map((number, index) => {
                const key = `example${index}`;
                return <Text key={key}>Content</Text>;
              })}
            </Box>
          </SheetMobile>
        </Layer>
      ) : null}
      <Box padding={2}>
        <Button
          accessibilityLabel="Show SheetMobile"
          color="red"
          onClick={() => setShowComponent(true)}
          size="lg"
          text="Show SheetMobile"
        />
      </Box>
    </DeviceTypeProvider>
  );
}

Full

A full SheetMobile fully fills the page. It completely covers the primary screen. When size is set to "full", SheetMobile requires a visible dismiss IconButton.

import { useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  FixedZIndex,
  Flex,
  Layer,
  SheetMobile,
  Text,
} from 'gestalt';

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

  return (
    <DeviceTypeProvider deviceType="mobile">
      {showComponent ? (
        <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
          <SheetMobile
            footer={
              <Flex gap={2} justifyContent="center">
                <Button
                  color="gray"
                  onClick={() => setShowComponent(false)}
                  text="Secondary"
                />
                <Button
                  color="red"
                  onClick={() => setShowComponent(false)}
                  text="Primary"
                />
              </Flex>
            }
            heading="Heading"
            onDismiss={() => setShowComponent(false)}
            primaryAction={{
              accessibilityLabel: 'Next page',
              label: 'Next',
              onClick: () => setShowComponent(false),
            }}
            size="full"
            subHeading="SubHeading"
          >
            <Box>
              {Array(100).map((number, index) => {
                const key = `example${index}`;
                return <Text key={key}>Content</Text>;
              })}
            </Box>
          </SheetMobile>
        </Layer>
      ) : null}
      <Box padding={2}>
        <Button
          accessibilityLabel="Show SheetMobile"
          color="red"
          onClick={() => setShowComponent(true)}
          size="lg"
          text="Show SheetMobile"
        />
      </Box>
    </DeviceTypeProvider>
  );
}

SheetMobile's header has a flexible configuration.

  • It requires a heading prop and an optional subHeading.
  • It can display navigation buttons (back and forward navigation) using the backIconButton and forwardIconButton.
  • It can display a dismiss button (showDismissButton prop) as well as a primary action button (primaryAction prop).
  • It can display either a center-aligned or start-aligned header.

See the following cases for reference.

Text-only header
import { useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  FixedZIndex,
  Flex,
  Icon,
  Layer,
  SheetMobile,
  Text,
} from 'gestalt';

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

  return (
    <DeviceTypeProvider deviceType="mobile">
      {showComponent ? (
        <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
          <SheetMobile
            align="center"
            heading="Start creating now"
            onDismiss={() => setShowComponent(false)}
            showDismissButton={false}
            size="auto"
            subHeading="Inspiration starts here"
          >
            <Flex
              alignItems="center"
              gap={12}
              height="100%"
              justifyContent="center"
            >
              <Flex alignItems="center" direction="column" gap={1}>
                <Box
                  alignItems="center"
                  color="secondary"
                  display="flex"
                  height={50}
                  justifyContent="center"
                  rounding={4}
                  width={50}
                >
                  <Icon accessibilityLabel="Pin" color="default" icon="pin" />
                </Box>
                <Text size="100" weight="bold">
                  Pin
                </Text>
              </Flex>
              <Flex alignItems="center" direction="column" gap={1}>
                <Box
                  alignItems="center"
                  color="secondary"
                  display="flex"
                  height={50}
                  justifyContent="center"
                  rounding={4}
                  width={50}
                >
                  <Icon accessibilityLabel="Pin" color="default" icon="board" />
                </Box>
                <Text size="100" weight="bold">
                  Board
                </Text>
              </Flex>
            </Flex>
          </SheetMobile>
        </Layer>
      ) : null}
      <Box padding={2}>
        <Button
          accessibilityLabel="Show SheetMobile"
          color="red"
          onClick={() => setShowComponent(true)}
          size="lg"
          text="Show SheetMobile"
        />
      </Box>
    </DeviceTypeProvider>
  );
}

With primary action
import { useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  FixedZIndex,
  Flex,
  IconButtonLink,
  Layer,
  SheetMobile,
  TextField,
} from 'gestalt';

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

  return (
    <DeviceTypeProvider deviceType="mobile">
      {showComponent ? (
        <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
          <SheetMobile
            align="center"
            footer={
              <Flex gap={2} justifyContent="between">
                <IconButtonLink
                  accessibilityLabel="This IconButton is an example of IconButton acting as a link"
                  href="https://www.pinterest.com"
                  icon="share"
                  target="blank"
                  tooltip={{ text: 'Link example' }}
                />
                <Flex gap={2}>
                  <Button color="gray" text="Secondary" />
                </Flex>
                <IconButtonLink
                  accessibilityLabel="This IconButton is an example of IconButton acting as a link"
                  href="https://www.pinterest.com"
                  icon="ellipsis"
                  target="blank"
                  tooltip={{ text: 'Link example' }}
                />
              </Flex>
            }
            heading="Create a new personal account"
            onDismiss={() => setShowComponent(false)}
            primaryAction={{
              accessibilityLabel: 'Next page',
              label: 'Next',
              onClick: () => {},
            }}
            showDismissButton={false}
          >
            <TextField
              autoComplete="username"
              id="header-example"
              label="Username"
              onChange={() => {}}
              placeholder="Please enter your username"
              type="text"
            />
          </SheetMobile>
        </Layer>
      ) : null}
      <Box padding={2}>
        <Button
          color="red"
          onClick={() => setShowComponent(true)}
          size="lg"
          text="Show SheetMobile"
        />
      </Box>
    </DeviceTypeProvider>
  );
}

Dismiss button

Optional in partial SheetMobiles but required in full variants.

import { useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  FixedZIndex,
  Layer,
  SheetMobile,
  Text,
} from 'gestalt';

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

  return (
    <DeviceTypeProvider deviceType="mobile">
      {showComponent ? (
        <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
          <SheetMobile
            heading="Heading"
            onDismiss={() => setShowComponent(false)}
          >
            <Text>Content</Text>
          </SheetMobile>
        </Layer>
      ) : null}
      <Box padding={2}>
        <Button
          accessibilityLabel="Show SheetMobile"
          color="red"
          onClick={() => setShowComponent(true)}
          size="lg"
          text="Show SheetMobile"
        />
      </Box>
    </DeviceTypeProvider>
  );
}

Back and forward navigation

Back and forward navigation enables SheetMobile to display sequential content in a step by step flow.

import { Fragment, useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  FixedZIndex,
  Layer,
  SheetMobile,
  Text,
} from 'gestalt';

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

  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);
  const ABOVE_PAGE_HEADER_ZINDEX = new CompositeZIndex([PAGE_HEADER_ZINDEX]);

  return (
    <DeviceTypeProvider deviceType="mobile">
      {showComponent ? (
        <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
          <SheetMobile
            backIconButton={
              page > 1
                ? {
                    accessibilityLabel: 'Previous page',
                    onClick: () => setPage((value) => value - 1),
                  }
                : undefined
            }
            forwardIconButton={
              page < 3
                ? {
                    accessibilityLabel: 'Next page',
                    onClick: () => setPage((value) => value + 1),
                  }
                : undefined
            }
            heading="Business guidelines"
            onDismiss={() => setShowComponent(false)}
            subHeading="Create Pins in no time with our flexible tools."
          >
            <Fragment>
              {page === 1 ? (
                <Text>
                  Upload images or videos. Create and edit Pins right from our
                  app or desktop site. You can make one Pin at a time, or upload
                  assets in bulk.
                </Text>
              ) : null}
              {page === 2 ? (
                <Text>
                  Add your product feed. Connect a product feed and we’ll turn
                  every product into its own Pin.
                </Text>
              ) : null}
              {page === 3 ? (
                <Text>
                  Publish from your site. Link your site’s RSS feed and we’ll
                  automatically create Pins for new images in the feed.
                </Text>
              ) : null}
            </Fragment>
          </SheetMobile>
        </Layer>
      ) : null}
      <Box padding={2}>
        <Button
          accessibilityLabel="Show SheetMobile"
          color="red"
          onClick={() => setShowComponent(true)}
          size="lg"
          text="Show SheetMobile"
        />
      </Box>
    </DeviceTypeProvider>
  );
}

SheetMobile's footer has a flexible configuration. footer prop accepts any kind of node. The footer can have up to two Buttons and another two IconButtons as shown in the example.

import { useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  FixedZIndex,
  Flex,
  IconButtonLink,
  Layer,
  SheetMobile,
  Text,
} from 'gestalt';

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

  return (
    <DeviceTypeProvider deviceType="mobile">
      {showComponent ? (
        <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
          <SheetMobile
            footer={
              <Flex gap={2} justifyContent="between">
                <IconButtonLink
                  accessibilityLabel="This IconButton is an example of IconButton acting as a link"
                  href="https://www.pinterest.com"
                  icon="share"
                  target="blank"
                  tooltip={{ text: 'Link example' }}
                />

                <Flex gap={2}>
                  <Button color="gray" text="Secondary" />
                  <Button color="red" text="Primary" />
                </Flex>

                <IconButtonLink
                  accessibilityLabel="This IconButton is an example of IconButton acting as a link"
                  href="https://www.pinterest.com"
                  icon="ellipsis"
                  target="blank"
                  tooltip={{ text: 'Link example' }}
                />
              </Flex>
            }
            heading="Heading"
            onDismiss={() => setShowComponent(false)}
            showDismissButton={false}
          >
            <Text>Content</Text>
          </SheetMobile>
        </Layer>
      ) : null}
      <Box padding={2}>
        <Button
          accessibilityLabel="Show SheetMobile"
          color="red"
          onClick={() => setShowComponent(true)}
          size="lg"
          text="Show SheetMobile"
        />
      </Box>
    </DeviceTypeProvider>
  );
}

Animation

By default, SheetMobile animates in (up), with the initial render process from the entry-point, and out (down), when the ESC key is pressed, the header close button is pressed, or the user clicks outside of the SheetMobile. 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:

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

For exposed onClick props where it's not possible to use SheetMobile.DismissingElement, the onClick passes "onDismissStart" as render props along with the event.

onClick={({ onDismissStart }) => onDismissStart() }

When using these render props, just pass the argument onDismissStart to your exit-point action elements or execute them on the onClick. In the example below, we've added the exit animation to the:

  • Back arrow icon button (header)
  • "Close" gray button (children)
  • "Close" red button (footer)
import { useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  FixedZIndex,
  Flex,
  Layer,
  SheetMobile,
} from 'gestalt';

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

  return (
    <DeviceTypeProvider deviceType="mobile">
      {showComponent ? (
        <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
          <SheetMobile
            footer={
              <SheetMobile.DismissingElement>
                {({ onDismissStart }) => (
                  <Flex gap={2} justifyContent="center">
                    <Button color="red" onClick={onDismissStart} text="Close" />
                  </Flex>
                )}
              </SheetMobile.DismissingElement>
            }
            forwardIconButton={{
              accessibilityLabel: 'Next page',
              onClick: ({ onDismissStart }) => onDismissStart(),
            }}
            heading="Heading"
            onDismiss={() => setShowComponent(false)}
            size="auto"
          >
            <SheetMobile.DismissingElement>
              {({ onDismissStart }) => (
                <Flex alignItems="center" height="100%" justifyContent="center">
                  <Button color="gray" onClick={onDismissStart} text="Close" />
                </Flex>
              )}
            </SheetMobile.DismissingElement>
          </SheetMobile>
        </Layer>
      ) : null}
      <Box padding={2}>
        <Button
          accessibilityLabel="Show SheetMobile"
          color="red"
          onClick={() => setShowComponent(true)}
          size="lg"
          text="Show SheetMobile"
        />
      </Box>
    </DeviceTypeProvider>
  );
}

Preventing close on outside click

By default, users can click outside a partial SheetMobile (on the overlay) to close it. This can be disabled by setting closeOnOutsideClick to "false".

import { useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  FixedZIndex,
  Layer,
  SheetMobile,
  Text,
} from 'gestalt';

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

  return (
    <DeviceTypeProvider deviceType="mobile">
      {showComponent ? (
        <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
          <SheetMobile
            closeOnOutsideClick={false}
            heading="Heading"
            onDismiss={() => setShowComponent(false)}
          >
            <Text>Content</Text>
          </SheetMobile>
        </Layer>
      ) : null}
      <Box padding={2}>
        <Button
          accessibilityLabel="Show SheetMobile"
          color="red"
          onClick={() => setShowComponent(true)}
          size="lg"
          text="Show SheetMobile"
        />
      </Box>
    </DeviceTypeProvider>
  );
}

Transitions

SheetMobile slides up from the bottom as the initial transition. However, transitions between sheets should follow the following patterns.

import { Fragment, useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  FixedZIndex,
  Layer,
  SheetMobile,
  Text,
} from 'gestalt';

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

  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);
  const ABOVE_PAGE_HEADER_ZINDEX = new CompositeZIndex([PAGE_HEADER_ZINDEX]);

  return (
    <DeviceTypeProvider deviceType="mobile">
      {showComponent ? (
        <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
          <SheetMobile
            backIconButton={
              page > 1
                ? {
                    accessibilityLabel: 'Previous page',
                    onClick: () => setPage((value) => value - 1),
                  }
                : undefined
            }
            forwardIconButton={
              page < 3
                ? {
                    accessibilityLabel: 'Next page',
                    onClick: () => setPage((value) => value + 1),
                  }
                : undefined
            }
            heading="Business guidelines"
            onDismiss={() => setShowComponent(false)}
            subHeading="Create Pins in no time with our flexible tools."
          >
            <Fragment>
              {page === 1 ? (
                <Text>
                  Upload images or videos. Create and edit Pins right from our
                  app or desktop site. You can make one Pin at a time, or upload
                  assets in bulk.
                </Text>
              ) : null}
              {page === 2 ? (
                <Text>
                  Add your product feed. Connect a product feed and we’ll turn
                  every product into its own Pin.
                </Text>
              ) : null}
              {page === 3 ? (
                <Text>
                  Publish from your site. Link your site’s RSS feed and we’ll
                  automatically create Pins for new images in the feed.
                </Text>
              ) : null}
            </Fragment>
          </SheetMobile>
        </Layer>
      ) : null}
      <Box padding={2}>
        <Button
          accessibilityLabel="Show SheetMobile"
          color="red"
          onClick={() => setShowComponent(true)}
          size="lg"
          text="Show SheetMobile"
        />
      </Box>
    </DeviceTypeProvider>
  );
}

Same size

If there's a transition between SheetMobile of the same size, the content transitions in place.

import { useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  FixedZIndex,
  Flex,
  Layer,
  SheetMobile,
  TextField,
} from 'gestalt';

export default function Example() {
  const [showComponent, setShowComponent] = useState(true);
  const [showComponentData, setShowComponentData] = useState(null);
  const [showNextData, setShowNextData] = useState(null);

  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);
  const ABOVE_PAGE_HEADER_ZINDEX = new CompositeZIndex([PAGE_HEADER_ZINDEX]);

  const resetShowNextData = (animationState) => {
    if (animationState === 'in') {
      setShowNextData(null);
    }
  };

  return (
    <DeviceTypeProvider deviceType="mobile">
      {showComponent && showComponentData === 'A' ? (
        <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
          <SheetMobile
            heading="Heading"
            onAnimationEnd={({ animationState }) => {
              resetShowNextData(animationState);

              if (animationState === 'out' && showNextData === 'B') {
                setShowComponentData('B');
                setShowComponent(true);
              }
              if (animationState === 'out' && !showNextData) {
                setShowComponentData(null);
              }
            }}
            onDismiss={() => {
              setShowComponent(false);
            }}
          >
            <Flex alignContent="center" height="100%" justifyContent="center">
              <SheetMobile.DismissingElement>
                {({ onDismissStart }) => (
                  <Button
                    color="red"
                    onClick={() => {
                      onDismissStart();
                      setShowNextData('B');
                    }}
                    text="Review form"
                  />
                )}
              </SheetMobile.DismissingElement>
            </Flex>
          </SheetMobile>
        </Layer>
      ) : null}
      {showComponent && showComponentData === 'B' ? (
        <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
          <SheetMobile
            backIconButton={{
              accessibilityLabel: 'Previous page',
              onClick: ({ onDismissStart }) => {
                onDismissStart();
                setShowNextData('A');
              },
            }}
            footer={
              <Flex gap={2} justifyContent="center">
                <SheetMobile.DismissingElement>
                  {({ onDismissStart }) => (
                    <Button
                      color="red"
                      onClick={() => {
                        onDismissStart();
                        setShowNextData(null);
                      }}
                      text="Submit"
                    />
                  )}
                </SheetMobile.DismissingElement>
              </Flex>
            }
            heading="Heading"
            onAnimationEnd={({ animationState }) => {
              resetShowNextData(animationState);

              if (animationState === 'out' && showNextData === 'A') {
                setShowComponentData('A');
                setShowComponent(true);
              }
            }}
            onDismiss={() => setShowComponent(false)}
            size="auto"
          >
            <Flex
              alignContent="center"
              direction="column"
              gap={4}
              justifyContent="center"
            >
              {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((x) => (
                <TextField
                  key={x}
                  id={`Input${x}`}
                  label={`Input${x}`}
                  onChange={() => {}}
                  placeholder={`Input${x}`}
                  type="text"
                />
              ))}
            </Flex>
          </SheetMobile>
        </Layer>
      ) : null}
      <Box padding={2}>
        <Button
          accessibilityLabel="Show SheetMobile"
          color="red"
          onClick={() => {
            setShowComponentData('A');
            setShowComponent(true);
          }}
          size="lg"
          text="Show SheetMobile"
        />
      </Box>
    </DeviceTypeProvider>
  );
}

Different size

If there's a transition between SheetMobile of different sizes or with a size set to "auto", where height adjusts to content, the initial sheet will slide down to close and the new sheet will slide up to open.

External handlers

SheetMobile consumes external handlers from GlobalEventsHandlerProvider.

Handlers:

  • onOpen: executed when SheetMobile opens
  • onClose: executed when SheetMobile closes

See GlobalEventsHandlerProvider for more information.

import { useCallback, useMemo, useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  DeviceTypeProvider,
  FixedZIndex,
  Flex,
  GlobalEventsHandlerProvider,
  Layer,
  SheetMobile,
} from 'gestalt';

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

  const PAGE_HEADER_ZINDEX = new FixedZIndex(10);
  const ABOVE_PAGE_HEADER_ZINDEX = new CompositeZIndex([PAGE_HEADER_ZINDEX]);

  const sheetMobileOnOpen = useCallback(
    () => console.log('On open handler'),
    []
  );
  const sheetMobileOnClose = useCallback(
    () => console.log('On close handler'),
    []
  );
  const sheetMobileHandlers = useMemo(
    () => ({ onOpen: sheetMobileOnOpen, onClose: sheetMobileOnClose }),
    [sheetMobileOnOpen, sheetMobileOnClose]
  );

  return (
    <GlobalEventsHandlerProvider sheetMobileHandlers={sheetMobileHandlers}>
      <DeviceTypeProvider deviceType="mobile">
        {showComponent ? (
          <Layer zIndex={ABOVE_PAGE_HEADER_ZINDEX}>
            <SheetMobile
              heading="Heading"
              onDismiss={() => setShowComponent(false)}
              size="auto"
            >
              <SheetMobile.DismissingElement>
                {({ onDismissStart }) => (
                  <Flex
                    alignItems="center"
                    height="100%"
                    justifyContent="center"
                  >
                    <Button
                      color="gray"
                      onClick={onDismissStart}
                      text="Close"
                    />
                  </Flex>
                )}
              </SheetMobile.DismissingElement>
            </SheetMobile>
          </Layer>
        ) : null}
        <Box padding={2}>
          <Button
            accessibilityLabel="Show SheetMobile"
            color="red"
            onClick={() => setShowComponent(true)}
            size="lg"
            text="Show SheetMobile"
          />
        </Box>
      </DeviceTypeProvider>
    </GlobalEventsHandlerProvider>
  );
}

Implementation guidelines

SheetMobile is a mobile only component; therefore, there shouldn't be instances of SheetMobile in desktop experiences. To enforce proper usage, SheetMobile only renders when DeviceTypeProvider wraps your app and deviceType prop is set to "mobile". Otherwise, it only renders "null".

Writing

Do
Don't
  • Punctuate headings unless they are posing a question or making an exclamation
  • Use Title Case or ALL CAPS

Component quality checklist

Component quality checklist
Quality item
Status
Status description
Figma Library
Component is not currently available in Figma.
Responsive Web
Component does not respond to changing viewport sizes in web and mobile web.

Internal documentation

Modal
Modal displays content that requires user interaction. Modals appear on a layer above the page and therefore block the content underneath, preventing users from interacting with anything else besides the Modal.

ModalAlert
ModalAlert is a simple modal dialog used to alert a user of an issue, or to request confirmation after a user-triggered action.

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