How to hack around Gestalt

Guidelines for customizing Gestalt components.

Disclaimer

For the vast majority of use cases, please do not use these techniques. Please explore building your custom UI element using our primitive components, like Box, TapArea, Pog, etc first! And feel free to ask us for help!

Gestalt's components enforce the design system by restricting possible usage. This is by design! However, in certain scenarios — experimental usage, usage on internal tools or other non-Pinner/M10n surfaces, etc — it may be necessary to circumvent those restrictions to build the desired experience. With that in mind, here are some common techniques and their trade-offs.

Creating new components

Forking components

When a Gestalt component doesn't quite match the desired design spec, a common idea is to fork the component: copy/paste the component's code into the target repo where it can be modified.
Pro:

  • Complete freedom to make whatever changes are desired

Con:

  • Duplicate code
  • Drift from the original component can make re-integration difficult/impossible
  • Heavy maintenance burden: any future updates to Gestalt (changes to color, rounding, etc) will need to be made manually, and will likely lead to a broken/outdated UI in the meantime
  • Dark mode and RTL support will need to be handled manually

Alternative: Chat with the Gestalt team about your needs and let's see how we can accommodate them. Often features that we don't support are for accessibility or other reasons — but we're happy to see how we can support you!

Custom components

Custom components can also be made from scratch, using native DOM elements and CSS/SCSS.
Pro:

  • Complete freedom to build whatever UI is desired

Con:

  • Adds to bundle size with custom stylesheets instead of taking advantage of Gestalt's common styles
  • Creates disjointed app feel by not working within the design system
  • Heavy maintenance burden: no support from Gestalt team for future updates
  • Dark mode and RTL support will need to be handled manually

Alternative: Chat with the Gestalt team about your needs and let's see how we can accommodate them. If we can't officially support your needs, at least use Gestalt primitives (Box, TapArea, etc) when building your custom UI to ensure that your feature is accessible and fits in with the rest of the design system.

Modifications to existing components

Box's dangerouslySetInlineStyle

Box provides an "escape hatch" prop, dangerouslySetInlineStyle, allowing for styles to be set directly on the component. Similarly, Icon, IconButton, and Pog provide the dangerouslySetSvgPath to allow for custom icons.

Pro:

  • Uses Gestalt components for all non-custom styles needed, taking advantage of our hashed classes for smaller stylesheets, adhering to the design system, and getting future upgrades for free
  • We track dangerouslySetInlineStyle usage, which informs future changes to Gestalt components

Con:

  • Doesn't support pseudo-classes, pseudo-elements, or animations
  • Overridden styles will not respond to dark mode or RTL

Alternative: When possible, stick to the styles available on Gestalt components natively. If you need to use a custom style, try to use the corresponding design token instead of a hard-coded value. If your design calls for unsupported styles, please feel free to contact us to chat about design options.

import { Box, Flex } from 'gestalt';
import { TOKEN_COLOR_PINK_FLAMINGLOW_400 } from 'gestalt-design-tokens';

export default function Example() {
  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <Box
        dangerouslySetInlineStyle={{
          __style: {
            backgroundColor: TOKEN_COLOR_PINK_FLAMINGLOW_400,
          },
        }}
        height={100}
        width={100}
      />
    </Flex>
  );
}

Wrapping components

Certain components have styles that can be overridden when wrapped by (or wrapped around) other components.

Pro:

  • Continues to use Gestalt components for all except the custom styles

Con:

  • Your UI may look disjointed with the rest of the design system
  • Overridden styles will not respond to dark mode or RTL

Alternative: When possible, stick to the styles available on Gestalt components natively. If your design calls for unsupported styles, please feel free to contact us to chat about design options.

import { Flex, Text } from 'gestalt';

export default function Example() {
  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <Text color="error">
        <span style={{ fontFamily: 'cursive' }}>Custom text</span>
      </Text>
    </Flex>
  );
}

Refs

Components that accept refs (e.g. Box, Button, etc) can be customized by manipulating the referenced element.

Pro:

  • Uses Gestalt components for all non-custom styles needed, taking advantage of our hashed classes for smaller stylesheets, adhering to the design system, and getting future upgrades for free
  • Directly targets the instance of the component rather than relying on the underlying DOM elements

Con:

  • Doesn't support pseudo-classes, pseudo-elements, or animations
  • Typically only targets the outermost element of the component; inner elements of complex components cannot be reached
  • Overridden styles will not respond to dark mode or RTL

Alternative: When possible, stick to the styles available on Gestalt components natively. If your design calls for unsupported styles, please feel free to contact us to chat about design options.

import { useEffect, useRef } from 'react';
import { Flex, TextField } from 'gestalt';

export default function Example() {
  const ref = useRef(null);

  useEffect(() => {
    if (ref.current) {
      ref.current.style.backgroundColor = 'aquamarine';
    }
  }, [ref]);

  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <TextField
        ref={ref}
        errorMessage="Please don't do this!"
        id="refExample"
        onChange={() => {}}
        readOnly
        value="Custom color"
      />
    </Flex>
  );
}

CSS selectors

It is possible to use CSS selectors to peer "under the hood" of Gestalt components and target the underlying DOM elements.
Pro:

  • It's CSS, so you can do whatever you want

Con:

  • This is very, very brittle and subject to breaking changes at any point; we do not consider breaking changes to the underlying DOM structure when determining semver so even patch changes could break your UI
  • Overridden styles will not respond to dark mode or RTL

Alternative: Absolutely anything — this is just about the worst way to hack Gestalt components. Your UI will break in the future. Consider using a ref if possible.