IconButtonFloating represents the primary or most common action on the screen. As the name suggests, it floats over the content and is always on top of everything on the screen. Similar to IconButton, the floating version uses icons instead of text to convey available actions. However, it is used when the action needs to be visible at all times in a sticky way where content can scroll underneath. IconButtonFloating remains in place on scroll.

By default, it has a circular shape with a floating elevation shadow style built-in. When pressed, it will open more related actions by triggering Dropdown or Modal.

IconButtonFloating is typically found in the Home feed, boards, and dashboards, allowing Pinners to perform core actions.

also known as Glyph button, Floating action button, FAB, Quick create

Figma:

Responsive:

Adaptive:

A11y:

Props

Component props
Name
Type
Default
accessibilityLabel
Required
string
-

String that clients such as VoiceOver will read to describe the icon button. Always localize the label. See Accessibility section for more info.

accessibilityPopupRole
Required
"menu" | "dialog"
-

Indicates whether this component displays a menu, such as Dropdown, or a dialog, like Popover, Modal or ModalAlert. See the Accessibility guidelines for details on proper usage.

icon
Required
Icon[icon]
-

Icon displayed in IconButtonFloating to convey the behavior of the component. Refer to our iconography library to see available icons.

onClick
Required
({
  event: SyntheticMouseEvent<HTMLButtonElement> | SyntheticKeyboardEvent<HTMLButtonElement>,
}) => void
-

Callback fired when the component is clicked, pressed or tapped.

tooltip
Required
{
  accessibilityLabel?: string,
  inline?: boolean,
  idealDirection?: "up" | "right" | "down" | "left",
  text: string,
  zIndex?: Indexable,
}
-

Adds a Tooltip on hover/focus of the IconButtonFloating. See the With Tooltip variant to learn more.

accessibilityControls
string
-

Specifies the id of an associated element (or elements) whose contents or visibility are controlled by IconButtonFloating so that screen reader users can identify the relationship between elements. See the Accessibility guidelines for details on proper usage.

accessibilityExpanded
boolean
-

Used to indicates that IconButtonFloating hides or exposes a Dropdown and details whether it is currently open or closed. See the Accessibility guidelines for details on proper usage.

dangerouslySetSvgPath
{ __path: string }
-

Defines a new icon different from the built-in Gestalt icons. See custom icon variant to learn more.

disabled
boolean
-

When disabled, IconButtonFloating looks inactive and cannot be interacted with.

selected
boolean
-

Indicates whether the associated dropdown is open or closed. Not used when IconButtonFloating opens a dialog.

Usage guidelines

When to use
  • To represent a primary or common action when it has to be visible all the time on the screen on top of everything.
  • Triggering a Modal or a Popover to complete a related task.
  • Only if it is the most suitable way to present a screen's high-emphasis action.
When not to use
  • There isn't a need for a fixed IconButtonFloating visible all the time on the screen.
  • To replace IconButton established patterns, such as navigation elements.

Best practices

Do

Use when an action has to be visible at all times in a sticky way where content can scroll underneath.

Don't

Layer notification badges on top of IconButtonFloating. This pattern is typically used on IconButtons as part of a navigation component. IconButtonFloating shouldn't contain notifications found elsewhere on a screen, as it can lead to cognitive and usability issues. Users with color-blinded vision could also miss the intention of the notification since it doesn't offer a visually supportive affordance besides color.

Do

Use IconButtonFloating for positive and supportive actions like Create, Help or Maximize.

Don't

Use IconButtonFloating for negative and destructive actions like Delete or Remove.

Accessibility

ARIA attributes

IconButtonFloating conveys the component behavior using iconography. IconButtonFloating requires accessibilityLabel, a text description for screen readers to announce and communicate the represented Icon. In the example below, the screen reader reads: "Create Pin menu". The label should describe the intent of the action, not the Icon itself. For example, use "Edit board" instead of "Pencil".

If IconButtonFloating is used as a control button to show/hide a Popover-based component, we recommend passing the following ARIA attributes to assist screen readers:

  • accessibilityControls: informs the screen reader that IconButtonFloating controls the display of a Dropdown. Not needed if IconButtonFloating opens a Modal or other dialog. This populates aria-controls.
  • accessibilityPopupRole: informs the screen reader that there’s either a menu component, like Dropdown, or a dialog component, like Modal or Popover, attached to IconButtonFloating. This populates aria-haspopup.
  • accessibilityExpanded: informs the screen reader whether an anchored Dropdown component is currently open or closed. This populates aria-expanded.

Keyboard interaction

IconButtonFloating should be contained within the role="contentinfo" container on a page. This gives screen reader users the ability to skip any main content and go directly to the action buttons using the rotor. If there are multiple IconButtonFloatings, they should all be contained within the role="contentinfo" container.

import { useRef, useState } from 'react';
import { Box, Dropdown, Flex, IconButtonFloating, Image, Mask } from 'gestalt';

const pins = [
  {
    color: '#2b3938',
    height: 316,
    src: 'https://i.ibb.co/sQzHcFY/stock9.jpg',
    width: 474,
    name: 'the Hang Son Doong cave in Vietnam',
  },
  {
    color: '#8e7439',
    height: 1081,
    src: 'https://i.ibb.co/zNDxPtn/stock10.jpg',
    width: 474,
    name: 'La Gran Muralla, Pekín, China',
  },
  {
    color: '#698157',
    height: 711,
    src: 'https://i.ibb.co/M5TdMNq/stock11.jpg',
    width: 474,
    name: 'Plitvice Lakes National Park, Croatia',
  },
  {
    color: '#4e5d50',
    height: 632,
    src: 'https://i.ibb.co/r0NZKrk/stock12.jpg',
    width: 474,
    name: 'Ban Gioc – Detian Falls : 2 waterfalls straddling the Vietnamese and Chinese border.',
  },
  {
    color: '#6d6368',
    height: 710,
    src: 'https://i.ibb.co/zmFd0Dv/stock13.jpg',
    width: 474,
    name: 'Border of China and Vietnam',
  },
  {
    color: '#2b3938',
    height: 316,
    src: 'https://i.ibb.co/sQzHcFY/stock9.jpg',
    width: 474,
    name: 'the Hang Son Doong cave in Vietnam',
  },
];

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

  const onSelect = ({ item }) => {
    if (selected.some(({ value }) => value === item.value)) {
      setSelected((selectedValue) =>
        selectedValue.filter(({ value }) => value !== item.value)
      );
    } else {
      setSelected((selectedValue) => [...selectedValue, item]);
    }
  };

  return (
    <Box margin={3}>
      <Box role="main">
        <Flex
          alignItems="center"
          gap={5}
          height="100%"
          justifyContent="center"
          width="100%"
          wrap
        >
          {[...new Array(3)].map(() =>
            pins.map((pin) => (
              <Mask key={pin.name} height={170} rounding={2} width={100}>
                <Image
                  alt={pin.name}
                  color="white"
                  fit="cover"
                  naturalHeight={1}
                  naturalWidth={1}
                  role="presentation"
                  src={pin.src}
                />
              </Mask>
            ))
          )}
        </Flex>
      </Box>
      <Box
        ref={anchorRef}
        bottom
        dangerouslySetInlineStyle={{
          __style: { left: '50%', transform: 'translate(-50%)' },
        }}
        marginBottom={2}
        position="fixed"
        role="contentinfo"
      >
        <IconButtonFloating
          accessibilityControls="sections-dropdown-example"
          accessibilityExpanded={open}
          accessibilityLabel="Create Pin Menu"
          accessibilityPopupRole="menu"
          icon="add"
          onClick={() => setOpen((prevVal) => !prevVal)}
          selected={open}
          tooltip={{ text: 'Create Pin Menu' }}
        />
      </Box>
      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="sections-dropdown-example"
          idealDirection="up"
          onDismiss={() => setOpen(false)}
        >
          <Dropdown.Section label="Create">
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'Pin', label: 'Pin' }}
              selected={selected}
            />
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'Story Pin', label: 'Story Pin' }}
              selected={selected}
            />
          </Dropdown.Section>
          <Dropdown.Section label="Add">
            <Dropdown.Item
              badge={{ text: 'New' }}
              onSelect={onSelect}
              option={{ value: 'Note', label: 'Note' }}
              selected={selected}
            />
          </Dropdown.Section>
        </Dropdown>
      )}
    </Box>
  );
}

Localization

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

Variants

Size

IconButtonFloating size is only available in a single size, 56px (the equivalent of IconButton's size='xl'). Keeping the size consistent will promote a cohesive Pinner experience and avoid usability issues.

import { useRef, useState } from 'react';
import { Box, Dropdown, Flex, IconButtonFloating, Image, Text } from 'gestalt';

const cards = [
  {
    title: 'Newsroom',
    description: "Today's Top Holiday Picks",
    src: 'https://i.ibb.co/FY2MKr5/stock6.jpg',
  },
  {
    title: 'Pinterest Trends',
    description: 'Checkout our 2023 predictions',
    src: 'https://i.ibb.co/sQzHcFY/stock9.jpg',
  },
];

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

  return (
    <Box margin={2}>
      <Flex
        alignItems="center"
        flex="grow"
        gap={4}
        justifyContent="center"
        width="100%"
        wrap
      >
        {cards.map((card) => (
          <Flex.Item key={card.title} flex="grow">
            <Box
              borderStyle="sm"
              color="default"
              minWidth={320}
              padding={4}
              rounding={4}
              width="100%"
            >
              <Flex
                alignContent="center"
                direction="column"
                gap={3}
                justifyContent="between"
              >
                <Text align="start" size="500">
                  {card.title}
                </Text>
                <Box height={170} width="100%">
                  <Image
                    alt=""
                    color="#000"
                    fit="cover"
                    naturalHeight={1}
                    naturalWidth={1}
                    role="presentation"
                    src={card.src}
                  />
                </Box>
                <Text align="start" size="300">
                  {card.description}
                </Text>
              </Flex>
            </Box>
          </Flex.Item>
        ))}
      </Flex>
      <Box
        ref={anchorRef}
        bottom
        margin={4}
        position="fixed"
        right
        role="contentinfo"
      >
        <IconButtonFloating
          accessibilityControls="sections-dropdown-example"
          accessibilityExpanded={open}
          accessibilityLabel="Help & Resources Menu"
          accessibilityPopupRole="menu"
          icon="question-mark"
          onClick={() => setOpen((prevVal) => !prevVal)}
          selected={open}
          tooltip={{
            text: 'Help & Resources Menu',
          }}
        />
      </Box>
      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="sections-dropdown-example"
          idealDirection="up"
          onDismiss={() => setOpen(false)}
        >
          <Dropdown.Link
            href="#"
            isExternal
            onClick={() => {
              /* log click here */
            }}
            option={{ value: 'Get help', label: 'Visit the Help Center' }}
          />
          <Dropdown.Link
            href="#"
            isExternal
            onClick={() => {
              /* log click here */
            }}
            option={{ value: 'Get help', label: 'Create widget' }}
          />
        </Dropdown>
      )}
    </Box>
  );
}

Placement

IconButtonFloating is always placed along the bottom of the screen. A consistent position improves discoverability when IconButtonFloating appears across a wide range of surfaces. For example, an IconButtonFloating used to open resources should remain in the same spot on the page across surfaces. In most cases, only one IconButtonFloating should be present on a screen. The exception is using a centered IconButtonFloating as a primary action, like Pin or board creation.

Bottom edge placement
IconButtonFloating should typically be placed in the bottom edge of the screen (bottom right for LTR languages, and bottom left for RTL languages). This applies to supportive actions, such as opening related content and resources.

Centered placement
Use a centered placement when leading Pinners to an action or task that should remain present when scrolling, such as creating a new board.

import { useRef, useState } from 'react';
import { Box, Dropdown, Flex, IconButtonFloating, Image, Text } from 'gestalt';

const cards = [
  {
    title: 'Newsroom',
    description: "Today's Top Holiday Picks",
    src: 'https://i.ibb.co/FY2MKr5/stock6.jpg',
  },
  {
    title: 'Pinterest Trends',
    description: 'Checkout our 2023 predictions',
    src: 'https://i.ibb.co/sQzHcFY/stock9.jpg',
  },
];

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

  return (
    <Box margin={2}>
      <Flex
        alignItems="center"
        flex="grow"
        gap={4}
        justifyContent="center"
        width="100%"
        wrap
      >
        {cards.map((card) => (
          <Flex.Item key={card.title} flex="grow">
            <Box
              borderStyle="sm"
              color="default"
              minWidth={320}
              padding={4}
              rounding={4}
              width="100%"
            >
              <Flex
                alignContent="center"
                direction="column"
                gap={3}
                justifyContent="between"
              >
                <Text align="start" size="500">
                  {card.title}
                </Text>
                <Box height={170} width="100%">
                  <Image
                    alt=""
                    color="#000"
                    fit="cover"
                    naturalHeight={1}
                    naturalWidth={1}
                    role="presentation"
                    src={card.src}
                  />
                </Box>
                <Text align="start" size="300">
                  {card.description}
                </Text>
              </Flex>
            </Box>
          </Flex.Item>
        ))}
      </Flex>
      <Box
        ref={anchorRef}
        bottom
        margin={4}
        position="fixed"
        right
        role="contentinfo"
      >
        <IconButtonFloating
          accessibilityControls="sections-dropdown-example"
          accessibilityExpanded={open}
          accessibilityLabel="Help & Resources Menu"
          accessibilityPopupRole="menu"
          icon="question-mark"
          onClick={() => setOpen((prevVal) => !prevVal)}
          selected={open}
          tooltip={{
            text: 'Help & Resources Menu',
          }}
        />
      </Box>
      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="sections-dropdown-example"
          idealDirection="up"
          onDismiss={() => setOpen(false)}
        >
          <Dropdown.Link
            href="#"
            isExternal
            onClick={() => {
              /* log click here */
            }}
            option={{ value: 'Get help', label: 'Visit the Help Center' }}
          />
          <Dropdown.Link
            href="#"
            isExternal
            onClick={() => {
              /* log click here */
            }}
            option={{ value: 'Get help', label: 'Create widget' }}
          />
        </Dropdown>
      )}
    </Box>
  );
}

Bottom edge placement
import { useRef, useState } from 'react';
import { Box, Dropdown, Flex, IconButtonFloating, Image, Mask } from 'gestalt';

const pins = [
  {
    color: '#2b3938',
    height: 316,
    src: 'https://i.ibb.co/sQzHcFY/stock9.jpg',
    width: 474,
    name: 'the Hang Son Doong cave in Vietnam',
  },
  {
    color: '#8e7439',
    height: 1081,
    src: 'https://i.ibb.co/zNDxPtn/stock10.jpg',
    width: 474,
    name: 'La Gran Muralla, Pekín, China',
  },
  {
    color: '#698157',
    height: 711,
    src: 'https://i.ibb.co/M5TdMNq/stock11.jpg',
    width: 474,
    name: 'Plitvice Lakes National Park, Croatia',
  },
  {
    color: '#4e5d50',
    height: 632,
    src: 'https://i.ibb.co/r0NZKrk/stock12.jpg',
    width: 474,
    name: 'Ban Gioc – Detian Falls : 2 waterfalls straddling the Vietnamese and Chinese border.',
  },
  {
    color: '#6d6368',
    height: 710,
    src: 'https://i.ibb.co/zmFd0Dv/stock13.jpg',
    width: 474,
    name: 'Border of China and Vietnam',
  },
  {
    color: '#2b3938',
    height: 316,
    src: 'https://i.ibb.co/sQzHcFY/stock9.jpg',
    width: 474,
    name: 'the Hang Son Doong cave in Vietnam',
  },
];

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

  const onSelect = ({ item }) => {
    if (selected.some(({ value }) => value === item.value)) {
      setSelected((selectedValue) =>
        selectedValue.filter(({ value }) => value !== item.value)
      );
    } else {
      setSelected((selectedValue) => [...selectedValue, item]);
    }
  };

  return (
    <Box margin={3}>
      <Box role="main">
        <Flex
          alignItems="center"
          gap={5}
          height="100%"
          justifyContent="center"
          width="100%"
          wrap
        >
          {[...new Array(3)].map(() =>
            pins.map((pin) => (
              <Mask key={pin.name} height={170} rounding={2} width={100}>
                <Image
                  alt={pin.name}
                  color="white"
                  fit="cover"
                  naturalHeight={1}
                  naturalWidth={1}
                  role="presentation"
                  src={pin.src}
                />
              </Mask>
            ))
          )}
        </Flex>
      </Box>
      <Box
        ref={anchorRef}
        bottom
        dangerouslySetInlineStyle={{
          __style: { left: '50%', transform: 'translate(-50%)' },
        }}
        marginBottom={2}
        position="fixed"
        role="contentinfo"
      >
        <IconButtonFloating
          accessibilityControls="sections-dropdown-example"
          accessibilityExpanded={open}
          accessibilityLabel="Create Pin Menu"
          accessibilityPopupRole="menu"
          icon="add"
          onClick={() => setOpen((prevVal) => !prevVal)}
          selected={open}
          tooltip={{ text: 'Create Pin Menu' }}
        />
      </Box>
      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="sections-dropdown-example"
          idealDirection="up"
          onDismiss={() => setOpen(false)}
        >
          <Dropdown.Section label="Create">
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'Pin', label: 'Pin' }}
              selected={selected}
            />
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'Story Pin', label: 'Story Pin' }}
              selected={selected}
            />
          </Dropdown.Section>
          <Dropdown.Section label="Add">
            <Dropdown.Item
              badge={{ text: 'New' }}
              onSelect={onSelect}
              option={{ value: 'Note', label: 'Note' }}
              selected={selected}
            />
          </Dropdown.Section>
        </Dropdown>
      )}
    </Box>
  );
}

Centered placement

With tooltip

IconButtonFloating requires a tooltip to provide additional context to the user about the action.

import { useRef, useState } from 'react';
import { Box, Dropdown, Flex, IconButtonFloating } from 'gestalt';

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

  const onSelect = ({ item }) => {
    if (selected.some(({ value }) => value === item.value)) {
      setSelected((selectedValue) =>
        selectedValue.filter(({ value }) => value !== item.value)
      );
    } else {
      setSelected((selectedValue) => [...selectedValue, item]);
    }
  };

  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <Box role="contentinfo">
        <IconButtonFloating
          ref={anchorRef}
          accessibilityControls="sections-dropdown-example"
          accessibilityExpanded={open}
          accessibilityLabel="Create Pin Menu"
          accessibilityPopupRole="menu"
          icon="add"
          onClick={() => setOpen((prevVal) => !prevVal)}
          selected={open}
          tooltip={{
            text: 'Create Pin Menu',
          }}
        />
      </Box>

      {open && (
        <Dropdown
          anchor={anchorRef.current}
          id="sections-dropdown-example"
          onDismiss={() => setOpen(false)}
        >
          <Dropdown.Section label="Create">
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'Pin', label: 'Pin' }}
              selected={selected}
            />
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'Story Pin', label: 'Story Pin' }}
              selected={selected}
            />
          </Dropdown.Section>
          <Dropdown.Section label="Add">
            <Dropdown.Item
              badge={{ text: 'New' }}
              onSelect={onSelect}
              option={{ value: 'Note', label: 'Note' }}
              selected={selected}
            />
            <Dropdown.Item
              onSelect={onSelect}
              option={{ value: 'Section', label: 'Section' }}
              selected={selected}
            />
          </Dropdown.Section>
        </Dropdown>
      )}
    </Flex>
  );
}

Writing

Do
  • Use a descriptive label to describe the IconButtonFloating action by beginning with a verb.
Don't
  • Use the words "image" or "icon" in the description label. Instead, prefer to use verbs that describe the action, e.g. "Save" or "Edit".

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.

IconButton
Use IconButton when only an icon is needed instead of text, and the action does not float over other content.

Button
Button allows users to take actions and make choices using text labels to express what action will occur when the user interacts with it.

Icon
IconButtonFloating uses icons instead of text to convey available actions on a screen. Use an existing icon from the Gestalt icon library.

Dropdown
IconButtonFloating is commonly paired with Dropdown to display a menu of options or actions.