FixedZIndex and CompositeZIndex are utility classes that generate z-indices for Gestalt components.

Figma:

Responsive:

Adaptive:

FixedZIndex

FixedZIndex is used for setting fixed z-index values. Use this class when you want to create an initial z-index to stack others on top of. FixedZIndex must be instantiated with a number.

import { FixedZIndex } from 'gestalt';

const fixedZindex = new FixedZIndex(1);

CompositeZIndex

CompositeZIndex is used for dynamically composing z-index values. Use this class to layer components on top of an existing z-index in the stacking context.

CompositeZIndex must be instantiated with an array of FixedZIndex or CompositeZIndex instances. CompositeZIndex returns the highest z-index value in the array +1.

import { CompositeZIndex, FixedZIndex } from 'gestalt';

const fixedZIndex = new FixedZIndex(1); //z-index value: 1

const compositeZIndex = new CompositeZIndex([fixedZIndex]); //z-index value: 2

const highestCompositeZIndex = new CompositeZIndex([fixedZIndex, compositeZIndex]); //z-index value: 3

Best practices

Do

Export FixedZIndex and CompositeZIndex rather than the z-index values itself.

// exporting file
import { FixedZIndex } from 'gestalt';

export const BaseZIndex = = new FixedZIndex(1);

// importing file
import { BaseZIndex } from './path/to/your/zindex/file';

const BoxWithZIndex = <Box zIndex={BaseZIndex}/>
Don't

Export constant z-index values to create FixedZIndex and CompositeZIndex.

// exporting file
export const BaseZIndex = 1;

// importing file
import { BaseZIndex } from './path/to/your/zindex/file';
import { FixedZIndex } from 'gestalt';

const BoxZIndex = new FixedZIndex(BaseZIndex);

const BoxWithZIndex = <Box zIndex={BoxZIndex}/>
Do

Use CompositeZIndex to compose z-indices.

// exporting file
import { FixedZIndex } from 'gestalt';

export const BaseZIndex = = new FixedZIndex(1);

// importing file
import { CompositeZIndex } from 'gestalt';

import { BaseZIndex } from './path/to/your/zindex/file';

const composedZIndex = new CompositeZIndex([BaseZIndex]);

const BoxWithZIndex = <Box zIndex={composedZIndex}/>
Don't

Use FixedZIndex to manually compose z-indices.

// exporting file
export const BaseZIndex = 1;

// importing file
import { FixedZIndex } from 'gestalt';
import { BaseZIndex } from './path/to/your/zindex/file';

const composedZIndex = new FixedZIndex(BaseZIndex + 1);

const BoxWithZIndex = <Box zIndex={composedZIndex}/>
Do

Use the lowest possible z-index values and compose them.

import { CompositeZIndex, FixedZIndex } from 'gestalt';

const PageHeaderZindex = new FixedZIndex(1);

const ArticleZindex =new FixedZIndex(1);

const ArticleHeaderZindex = new CompositeZIndex([ArticleZindex]);

const SheetZindex = new CompositeZIndex([PageHeaderZindex, ArticleHeaderZindex]);

const modalZindex = new CompositeZIndex([SheetZindex]);

const ModalWithZIndex = <Layer zIndex={modalZindex}><Modal/></Layer>
Don't

Use unnecessarily high fixed z-index values.

import { FixedZIndex } from 'gestalt';

const HeaderZindex = new FixedZIndex(1);

const MenuZindex =new FixedZIndex(1000);

const SheetZindex = new FixedZIndex(9999);

const modalZindex = new FixedZIndex(100000);

const ModalWithZIndex = <Layer zIndex={modalZindex}><Modal/></Layer>
Do

Wrap non-Gestaltifiable HTML tags in Box and pass z-index classes.

import { FixedZIndex } from 'gestalt';

const fixedZindex = new FixedZIndex(1);

const canvas = (
    <Box zIndex={fixedZindex}>
      <canvas
        id="myCanvas"
        width="200"
        height="100"
        style="border:1px solid #000000;"></canvas>
    </Box>
);
Don't

Extract z-index values from either z-index classes to use in non-Gestaltifiable HTML Tags. See z-index in non-Gestalt components to learn more.

import { FixedZIndex } from 'gestalt';

const fixedZindex = new FixedZIndex(1);

const canvas = (
  <canvas
    z-index={fixedZindex.index()}
    id="myCanvas"
    width="200"
    height="100"
    style="border:1px solid #000000;"></canvas>
);
Do

Use Layer instead of z-index when a component needs to visually break out of its parent container, for example, in the case of Modals, OverlayPanels and Tooltips. See Z-Index in foundational components and Z-Index in Layer to learn more.

const modal = (
  <Layer>
    <Modal>
      <Text>Modal</Text>
    </Modal>
  </Layer>
);
Don't

Use Box with high fixed z-index values to position your components at the top of the stacking context or use them redundantly with Layer. See z-Index in foundational components and ZIndex in Layer to learn more.

import { FixedZIndex } from 'gestalt';

const MODAL_ZINDEX = new FixedZIndex(1000000);

const modalA = (
  <Box zIndex={MODAL_ZINDEX}>
    <Modal>
      <Text>Modal A</Text>
    </Modal>
  <Box>
);

const modalB = (
  <Layer>
    <Box zIndex={MODAL_ZINDEX}>
      <Modal>
        <Text>Modal B</Text>
      </Modal>
    <Box zIndex={MODAL_ZINDEX}>
  </Layer>
);

Accessibility

Variants

z-index in foundational components

Box, Sticky, and Layer are foundational components that have zIndex props. If any other components need to be positioned within their stacking context, wrap with one of these foundational components to set the z-index.

Layer creates a new stacking context. Unless there's a conflict with another z-index, don't pass unnecessary zIndex to Layer.

The following example sets a z-index in the Layer wrapping OverlayPanel to position OverlayPanel over the page header in the Docs. Set PAGE_HEADER_ZINDEX below 10 to see the importance of z-index in this example.

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, useEffect, useRef, useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  FixedZIndex,
  Flex,
  Image,
  Layer,
  Mask,
  OverlayPanel,
  Popover,
  SearchField,
  TapArea,
  Text,
  TextArea,
} from 'gestalt';

/* ======= Z-INDEX  ======= */
const PAGE_HEADER_ZINDEX = new FixedZIndex(10);
const SHEET_ZINDEX = new CompositeZIndex([PAGE_HEADER_ZINDEX]);

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

  useEffect(() => {
    _optionalChain([
      ref,
      'access',
      (_) => _.current,
      'optionalAccess',
      (_2) => _2.focus,
      'call',
      (_3) => _3(),
    ]);
  }, []);

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

function List({ title, onSelect }) {
  return (
    <Flex direction="column" gap={{ column: 4, row: 0 }}>
      <Text color="default" size="100">
        {title}
      </Text>

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

              <Text align="center" color="default" weight="bold">
                {data[1]}
              </Text>
            </Flex>
          </TapArea>
        ))}
      </Flex>
    </Flex>
  );
}

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

  const handleSelect = (data) => {
    setSelectedBoard(data);
    setOpenPopover(false);
  };

  return (
    <Fragment>
      <Flex direction="column" gap={{ column: 2, row: 0 }}>
        <Text size="100">Board</Text>

        <Button
          ref={anchorRef}
          accessibilityLabel="Select Board"
          iconEnd="arrow-down"
          onClick={() => setOpenPopover(!openPopover)}
          text={selectedBoard}
        />
      </Flex>

      {openPopover && (
        <Layer>
          <Popover
            anchor={anchorRef.current}
            idealDirection="down"
            onDismiss={() => setOpenPopover(false)}
            positionRelativeToAnchor={false}
            size="xl"
          >
            <Box width={360}>
              <Box
                flex="grow"
                marginBottom={8}
                marginEnd={4}
                marginStart={4}
                marginTop={6}
              >
                <Flex direction="column" gap={{ column: 6, row: 0 }}>
                  <Text align="center" color="default" weight="bold">
                    Save to board
                  </Text>
                  <SearchBoardField />
                </Flex>
              </Box>

              <Box height={300} overflow="scrollY">
                <Box marginEnd={4} marginStart={4}>
                  <Flex direction="column" gap={{ column: 8, row: 0 }}>
                    <List onSelect={handleSelect} title="Top choices" />
                    <List onSelect={handleSelect} title="All boards" />
                  </Flex>
                </Box>
              </Box>
            </Box>
          </Popover>
        </Layer>
      )}
    </Fragment>
  );
}

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

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Button onClick={() => setShowSheet(true)} size="lg" text="Edit Pin" />

      {showSheet && (
        <Layer zIndex={SHEET_ZINDEX}>
          <OverlayPanel
            accessibilityDismissButtonLabel="Close edit Pin overlay panel"
            accessibilityLabel="Edit your Pin details"
            footer={
              <Flex>
                <Flex.Item flex="grow">
                  <Button
                    color="white"
                    onClick={() => setShowSheet(false)}
                    size="lg"
                    text="Delete"
                  />
                </Flex.Item>

                <Flex gap={{ column: 0, row: 2 }}>
                  <Button
                    onClick={() => setShowSheet(false)}
                    size="lg"
                    text="Cancel"
                  />

                  <Button
                    color="red"
                    onClick={() => setShowSheet(false)}
                    size="lg"
                    text="Done"
                    type="submit"
                  />
                </Flex>
              </Flex>
            }
            heading="Edit Pin"
            onDismiss={() => setShowSheet(false)}
            size="lg"
          >
            <Box display="flex" height={400} paddingX={8}>
              <Flex gap={{ row: 8, column: 0 }} width="100%">
                <Box paddingX={2} rounding={4} width={200}>
                  <Mask rounding={4}>
                    <Image
                      alt="Tropic greens: The taste of Petrol and Porcelain | Interior design, Vintage Sets and Unique Pieces agave"
                      color="rgb(231, 186, 176)"
                      naturalHeight={751}
                      naturalWidth={564}
                      src="https://i.ibb.co/7bQQYkX/stock2.jpg"
                    />
                  </Mask>
                </Box>

                <Flex.Item flex="grow">
                  <Flex direction="column" gap={{ column: 8, row: 0 }}>
                    <SelectBoard />

                    <TextArea
                      id="note"
                      label="Note"
                      onChange={() => {}}
                      placeholder="Add note"
                      value=""
                    />
                  </Flex>
                </Flex.Item>
              </Flex>
            </Box>
          </OverlayPanel>
        </Layer>
      )}
    </Box>
  );
}

z-index in Layer

Modal and OverlayPanel always require a parent Layer to position themselves outside the DOM hierarchy.

Components built on top of Popover, such as Tooltip, Dropdown and ComboBox, have a built-in Layer to be positioned outside the DOM hierarchy. To set the internal z-index value of Layer, these Popover-based components have zIndex props as well. This is used when placing the Popover-based components within another component wrapped in Layer that has a z-index set.

However, Modal and OverlayPanel have a built-in ScrollBoundaryContainer wrapping their children, so you shouldn’t need to pass z-index values when using Popover-based children.

The following example sets a z-index in the Layer wrapping Modal to position Modal over the page header in the Docs. Thanks to ScrollBoundaryContainer, child Tooltips don't require z-index.

import { Fragment, useState } from 'react';
import {
  Box,
  Button,
  CompositeZIndex,
  FixedZIndex,
  Flex,
  Heading,
  IconButton,
  Image,
  Layer,
  Modal,
  Text,
  Tooltip,
} from 'gestalt';

/* ======= Z-INDEX  ======= */
const PAGE_HEADER_ZINDEX = new FixedZIndex(10);
const MODAL_ZINDEX = new CompositeZIndex([PAGE_HEADER_ZINDEX]);

export default function ScrollBoundaryContainerExample() {
  const [showModal, setShowModal] = useState(false);
  const [alignText, setAlignText] = useState('forceLeft');

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Fragment>
        <Box display="flex" justifyContent="center">
          <Button
            accessibilityLabel="Edit this Pin"
            color="gray"
            onClick={() => setShowModal(true)}
            size="lg"
            text="Open edit modal"
          />
        </Box>
        {showModal && (
          <Layer zIndex={MODAL_ZINDEX}>
            <Modal
              accessibilityModalLabel="Edit Pin"
              footer={
                <Box flex="grow" paddingX={3} paddingY={3}>
                  <Box
                    display="flex"
                    justifyContent="end"
                    marginBottom={-1}
                    marginEnd={-1}
                    marginStart={-1}
                    marginTop={-1}
                    wrap
                  >
                    <Box paddingX={1} paddingY={1}>
                      <Button
                        onClick={() => setShowModal(false)}
                        size="lg"
                        text="Cancel"
                      />
                    </Box>
                    <Box paddingX={1} paddingY={1}>
                      <Button
                        color="red"
                        onClick={() => setShowModal(false)}
                        size="lg"
                        text="Save"
                        type="submit"
                      />
                    </Box>
                  </Box>
                </Box>
              }
              heading="Edit"
              onDismiss={() => setShowModal(false)}
              size="lg"
            >
              <Box column={12} display="flex" justifyContent="center">
                <Box column={6} paddingX={4}>
                  <Image
                    alt="Tropic greens: The taste of Petrol and Porcelain | Interior design, Vintage Sets and Unique Pieces agave"
                    color="rgb(231, 186, 176)"
                    naturalHeight={751}
                    naturalWidth={564}
                    src="https://i.ibb.co/7bQQYkX/stock2.jpg"
                  >
                    <Box padding={3}>
                      <Heading align={alignText} color="light" size="500">
                        Tropic greens: The taste of Petrol and Porcelain
                      </Heading>
                    </Box>
                  </Image>
                </Box>
                <Flex direction="column" gap={{ column: 4, row: 0 }}>
                  <Heading size="400">Text Overlay</Heading>
                  <Text size="300">Add text directly onto your Pin</Text>
                  <Text size="300" weight="bold">
                    Alignment
                  </Text>
                  <Flex>
                    <Tooltip text="Align left">
                      <IconButton
                        accessibilityLabel="Align left"
                        bgColor="white"
                        icon="text-align-left"
                        iconColor="darkGray"
                        onClick={() => setAlignText('left')}
                        selected={alignText === 'left'}
                        size="lg"
                      />
                    </Tooltip>
                    <Tooltip text="Align center">
                      <IconButton
                        accessibilityLabel="Align center"
                        bgColor="white"
                        icon="text-align-center"
                        iconColor="darkGray"
                        onClick={() => setAlignText('center')}
                        selected={alignText === 'center'}
                        size="lg"
                      />
                    </Tooltip>
                    <Tooltip text="Align right">
                      <IconButton
                        accessibilityLabel="Align right"
                        bgColor="white"
                        icon="text-align-right"
                        iconColor="darkGray"
                        onClick={() => setAlignText('right')}
                        selected={alignText === 'right'}
                        size="lg"
                      />
                    </Tooltip>
                  </Flex>
                </Flex>
              </Box>
            </Modal>
          </Layer>
        )}
      </Fragment>
    </Box>
  );
}

z-index in non-Gestalt components

FixedZIndex and CompositeZIndex work with Gestalt components. To stay consistent across the codebase using Gestalt’s zIndex classes, you can extract z-index values from both FixedZIndex and CompositeZIndex in cases where the component doesn’t accept Gestalt’s z-index classes.

import { FixedZIndex, CompositeZIndex } from 'gestalt';

const fixedZindex = new FixedZIndex(1);
const fixedZindexValue = fixedZindex.index(); // 1

const compositeZIndex = new CompositeZIndex([fixedZindex]);
const compositeZIndexValue = compositeZIndex.index(); // 2

However, this is an escape hatch that should only be used when the source code is not accessible, such as working with a third-party library. Otherwise, a better approach is to wrap the component or HTML element that needs a z-index in a Box. See Best Practices for more info.

Component quality checklist

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

Layer
Layer allows you to render children outside the DOM hierarchy of the parent. This is useful for places you might otherwise need to use z-index to overlay the screen, simplifying the stacking context complexity in the app. See z-Index in foundational components and ZIndex in Layer to learn more.