TapArea allows components to be clickable and touchable in an accessible way

also known as Touchable

Figma:

Responsive:

Adaptive:

A11y:

Props

Component props
Name
Type
Default
accessibilityChecked
boolean
-
accessibilityControls
string
-

Specify the id of an associated element (or elements) whose contents or visibility are controlled by a button component so that screen reader users can identify the relationship between elements.

Optional with type="button".

It populates aria-controls.'

accessibilityExpanded
boolean
-

Indicate that a button component hides or exposes collapsible components and expose whether they are currently expanded or collapsed.

Optional with type="button".

It populates aria-expanded.

accessibilityHaspopup
boolean
-

Indicate that a button component controls the appearance of interactive popup elements, such as menu or dialog

Optional with type="button".

It populates aria-haspopup.

accessibilityLabel
string
-

Supply a short, descriptive label for screen-readers to replace TapArea texts that do not provide sufficient context about the button component behavior.

It populates aria-label.

children
React.Node
-

TapArea is a wrapper around non-button components (or children) that provides clicking / touching functionality as if they were a unified button area.

dataTestId
string
-

Available for testing purposes, if needed.
Consider better queries before using this prop.

disabled
boolean
false

Set disabled state so TapArea cannot be interacted with and actions are not available.

fullHeight
boolean
-

Set the TapArea height to expand to the full height of the parent.

fullWidth
boolean
true

Set the TapArea width to expand to the full width of the parent.

mouseCursor
"copy"
| "default"
| "grab"
| "grabbing"
| "move"
| "noDrop"
| "pointer"
| "zoomIn"
| "zoomOut"
"pointer"

Select a mouse cursor type to convey the TapArea expected behavior.

onBlur
({
  event: SyntheticFocusEvent<HTMLDivElement>,
}) => void
-

Callback fired when a TapArea component loses focus.

onFocus
({
  event: SyntheticFocusEvent<HTMLDivElement>,
}) => void
-

Callback fired when a TapArea component gets focus via keyboard navigation, mouse click (pressed), or focus method.

onKeyDown
({
  event: SyntheticKeyboardEvent<HTMLDivElement>,
}) => void
-
onMouseDown
({
  event: SyntheticMouseEvent<HTMLDivElement>,
}) => void
-

Callback fired when a click event begins.

onMouseEnter
({
  event: SyntheticMouseEvent<HTMLDivElement>,
}) => void
-

Callback fired when a mouse pointer moves onto a TapArea component.

onMouseLeave
({
  event: SyntheticMouseEvent<HTMLDivElement>,
}) => void
-

Callback fired when a mouse pointer moves out a TapArea component.

onMouseUp
({
  event: SyntheticMouseEvent<HTMLDivElement>,
}) => void
-

Callback fired when a click event ends.

onTap
({
  event: SyntheticMouseEvent<HTMLDivElement> | SyntheticKeyboardEvent<HTMLDivElement>,
}) => void
-

Callback fired when a TapArea component is clicked (pressed and released) with a mouse or keyboard.

Required with type="button".

ref
HTMLDivElement
-

Ref that is forwarded to the underlying div element.

role
"button" | "switch"
-

Select 'button' when TapArea acts like regular buttons and 'switch' when the TapArea represents the states "on" and "off."

rounding
Rounding
0

Sets a border radius for the TapArea. Select a rounding option that aligns with its children.

Options are "circle" or "pill" for fully rounded corners or 0-8 representing the radius in boints.

tabIndex
-1 | 0
0

Remove the component from sequential keyboard navigation to improve accessibility. The component is not focusable with keyboard navigation but it can be focused with Javascript or visually by clicking with the mouse.

The default behaviour for the component is to be focusable in sequential keyboard navigation in the order defined by the document's source order.

If component is disabled, the component is also unreachable from keyboard navigation.

tapStyle
"none" | "compress"
"none"

Set a compressing behavior when the TapArea is clicked / touched.

  • 'none' does not compress TapArea. - 'compress' scales down TapArea.`

Accessibility

ARIA attributes

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

export default function MenuButtonExample() {
  const [selected, setSelected] = useState(false);
  const anchorRef = useRef(null);

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Fragment>
        <TapArea
          accessibilityControls="menu"
          accessibilityExpanded={selected}
          accessibilityHaspopup
          accessibilityLabel="Open the options menu"
          onTap={() => setSelected(!selected)}
        >
          <Box
            ref={anchorRef}
            alignItems="center"
            borderStyle="sm"
            display="inlineBlock"
            padding={2}
            rounding={1}
          >
            <Flex gap={{ column: 0, row: 2 }}>
              <Box height={50} width={50}>
                <Mask rounding={1}>
                  <Image
                    alt="Antelope Canyon"
                    naturalHeight={1}
                    naturalWidth={1}
                    src="https://i.ibb.co/FY2MKr5/stock6.jpg"
                  />
                </Mask>
              </Box>
              <Text align="center" weight="bold">
                Menu
              </Text>
            </Flex>
          </Box>
        </TapArea>
        {selected && (
          <Layer>
            <Popover
              anchor={anchorRef.current}
              idealDirection="down"
              onDismiss={() => setSelected(false)}
              positionRelativeToAnchor={false}
              size="md"
            >
              <Box direction="column" display="flex" id="menu" padding={2}>
                <Box padding={2}>
                  <Text weight="bold">Option 1</Text>
                </Box>
                <Box padding={2}>
                  <Text weight="bold">Option 2</Text>
                </Box>
              </Box>
            </Popover>
          </Layer>
        )}
      </Fragment>
    </Box>
  );
}

Localization

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

Variants

Link/Button within TapArea

import { useState } from 'react';
import { Box, Image, Link, Mask, TapArea, Text } from 'gestalt';

export default function TapAreaExample() {
  const [touches, setTouches] = useState(0);

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Box width={200}>
        <TapArea onTap={() => setTouches(touches + 1)} rounding={2}>
          <Box borderStyle="sm" color="selected" rounding={4}>
            <Mask rounding={2}>
              <Image
                alt="Antelope Canyon"
                naturalHeight={1}
                naturalWidth={1}
                src="https://i.ibb.co/DwYrGy6/stock14.jpg"
              />
            </Mask>
            <Box paddingY={2}>
              <Link
                href="https://www.pinterest.com/search/pins/?rs=ac&len=2&q=antelope%20canyon%20arizona&eq=Antelope%20Canyon"
                onClick={({ event }) => event.stopPropagation()}
                rounding="pill"
                target="blank"
              >
                <Text align="center" color="inverse">
                  Find More on Pinterest
                </Text>
              </Link>
            </Box>
          </Box>
        </TapArea>
        <Box paddingY={2}>
          <Text align="center" color="subtle">
            Touched {touches} {touches === 1 ? 'time' : 'times'}
          </Text>
        </Box>
      </Box>
    </Box>
  );
}

If you have a Link or Button inside of TapArea, you can apply e.stopPropagation() so the onTap doesn't get triggered.

TapArea with link interaction can be paired with GlobalEventsHandlerProvider. See GlobalEventsHandlerProvider to learn more about link navigation.

Compress behavior

import { useState } from 'react';
import { Box, Flex, Image, Label, Mask, Switch, TapArea, Text } from 'gestalt';

export default function Example() {
  const [disabled, setDisabled] = useState(false);
  const [compressed, setCompressed] = useState('compress');
  const [touches, setTouches] = useState(0);
  const [tabIndex, setTabIndex] = useState(false);

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Flex alignItems="start" direction="column" gap={{ column: 6, row: 0 }}>
        <Flex gap={6} wrap>
          <TapArea
            disabled={disabled}
            onTap={() => setTouches(touches + 1)}
            tabIndex={tabIndex ? -1 : 0}
            tapStyle={compressed}
          >
            <Box borderStyle="lg" column={12} padding={3} width={200}>
              <Mask rounding={2}>
                <Image
                  alt="Antelope Canyon"
                  naturalHeight={1}
                  naturalWidth={1}
                  src="https://i.ibb.co/DwYrGy6/stock14.jpg"
                />
              </Mask>
              <Text align="center">
                Touched {touches} {touches === 1 ? 'time' : 'times'}
              </Text>
            </Box>
          </TapArea>
        </Flex>
        <Flex gap={{ column: 0, row: 2 }}>
          <Switch
            id="compress-buttons"
            onChange={() =>
              setCompressed(compressed === 'compress' ? 'none' : 'compress')
            }
            switched={compressed === 'compress'}
          />
          <Box flex="grow" paddingX={2}>
            <Label htmlFor="compress-buttons">
              <Text>Compress TapArea</Text>
            </Label>
          </Box>
        </Flex>
        <Flex gap={{ column: 0, row: 2 }}>
          <Switch
            id="disable-buttons"
            onChange={() => setDisabled(!disabled)}
            switched={disabled}
          />
          <Box flex="grow" paddingX={2}>
            <Label htmlFor="disable-buttons">
              <Text>Disable TapArea</Text>
            </Label>
          </Box>
        </Flex>
        <Flex gap={{ column: 0, row: 2 }}>
          <Switch
            id="unreachable-buttons"
            onChange={() => setTabIndex(!tabIndex)}
            switched={tabIndex}
          />
          <Box flex="grow" paddingX={2}>
            <Label htmlFor="unreachable-buttons">
              <Text>Remove from keyboard navigation with tabIndex</Text>
            </Label>
          </Box>
        </Flex>
      </Flex>
    </Box>
  );
}

Height & width

import { Box, Flex, TapArea, Text } from 'gestalt';

export default function Example() {
  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Flex gap={6} height={250} maxWidth={500} wrap>
        <Box borderStyle="sm" height="100%" margin={3} width="100%">
          <TapArea fullHeight>
            <Box color="secondary" height="100%">
              <Text align="center">Full parent height</Text>
            </Box>
          </TapArea>
        </Box>
        <Box borderStyle="sm" height="100%" margin={3} width="100%">
          <TapArea>
            <Box color="secondary" height="100%">
              <Text align="center">Child height only</Text>
            </Box>
          </TapArea>
        </Box>
      </Flex>
    </Box>
  );
}

Full space with no children
import { Box, TapArea } from 'gestalt';

export default function Example() {
  return (
    <Box color="secondary" height="100%" width="100%">
      <TapArea fullHeight fullWidth onTap={() => {}} />
    </Box>
  );
}

Inline usage

import { Box, Flex, TapArea, Text } from 'gestalt';

export default function Example() {
  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Box color="warningBase" height={250} maxWidth={500} padding={3}>
        <Flex direction="column" gap={{ column: 6, row: 0 }}>
          <Flex.Item>
            <Text color="inverse" inline>
              Other content
            </Text>
            <Box borderStyle="sm" column={6} margin={3}>
              <TapArea>
                <Box color="secondary" height="100%">
                  <Text align="center">Default behavior (block)</Text>
                </Box>
              </TapArea>
            </Box>
          </Flex.Item>

          <Flex.Item>
            <Text color="inverse" inline>
              Other content
            </Text>
            <Box borderStyle="sm" column={6} display="inlineBlock" margin={3}>
              <TapArea>
                <Box color="secondary" height="100%">
                  <Text align="center">Inline behavior</Text>
                </Box>
              </TapArea>
            </Box>
          </Flex.Item>
        </Flex>
      </Box>
    </Box>
  );
}

While TapArea doesn't provide an inline prop, this behavior can be achieved by wrapping with <Box display="inlineBlock">.

Mouse cursor

Change the cursor on TapArea for different click interactions

Rounding

In ordee to observe TapArea's border radius, focus on each component below navigating with the keyboard. fullWidth={false} might be required to wrap to the children component. Make the sure the children components match the rounding as well.

rounding={0}
rounding={1}
rounding={2}
rounding={3}
rounding={4}
rounding={5}
rounding={6}
rounding={7}
rounding={8}
rounding="circle"
rounding="pill"

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.

Internal documentation

TapAreaLink
Use TapAreaLink when a link is needed instead of an action.