Using Styled Components

To apply styles to HTML, we use a CSS-in-JS library called Emotion. This allows us to define styles in the same file where the component is defined and used. Styles are scoped to avoid global selectors and having to manually specify classnames as strings. You can also leverage the theme object to reference common values and utilities.

The best styles are the ones you don’t write - whenever possible use existing components.

To use Style Components, import styled from @emotion/styled and apply custom CSS. For consistent spacing you should also use space():

Copied
import styled from '@emotion/styled';
import {space} from 'sentry/styles/space';

export default ExampleComponent() {
  return (
    <SideBySide>
      <p>a number: <Numeric>1</Numeric></p>
      <p>and another: <Numeric>2</Numeric></p>
    </SideBySide>
  )
}

const Numeric = styled('span')`
  font-variant-numeric: tabular-nums;
`;

const SideBySide = styled('div')`
  display: flex;
  gap: ${space(2)};
  flex-wrap: wrap;
  align-items: flex-start;
`;

Take constants (z-indexes, paddings, colors) from props.theme

Copied
import styled from "@emotion/styled";
import { space } from "sentry/styles/space";

const SomeComponent = styled("div")`
  border-radius: 1.45em;
  font-weight: bold;
  z-index: ${(p) => p.theme.zIndex.modal};
  padding: ${space(1)} ${space(2)};
  border: 1px solid ${(p) => p.theme.borderLight};
  color: ${(p) => p.theme.purple};
  box-shadow: ${(p) => p.theme.dropShadowHeavy};
`;

You can pass in custom props to your custom component and use that to set styles. This does create overhead at runtime and should be avoided. What follows is an example of how to pass custom props, and how to avoid doing so.

Copied
import styled from "@emotion/styled";

// ❌ Try to avoid this
const Label = styled("label")<{ isDisabled: boolean; isError: boolean }>`
  color: ${(p) =>
    p.isDisabled
      ? p.theme.gray200
      : p.isError
        ? p.theme.error400
        : inherit};
`;

return (
  <Label disabled={false} isError={true}>
    There is an error
  </Label>
);

Choose one of the strategies below instead.

Data selectors can be an easy way to set styles based on simple/pre-defined state. When defining these attributes, don't forget that browsers have built-in selectors like :disabled, :focus, :first-child and so on.

Copied
import styled from "@emotion/styled";

// ✅ Use a data attribute to pass in your state, and leverage CSS selectors:
const Label = styled("label")`
  color: inherit;
  &[data-is-error="true"] {
    color: ${(p) => p.theme.error400};
  }

  &:disabled {
    color: ${(p) => p.theme.gray200};
  }
`;

return (
  <Label disabled={false} data-is-error={true}>
    There is an error
  </Label>
);

The style and css attributes can be used, but the values of style are not linted. Avoid setting too many values at a time for readability.

Copied
import styled from "@emotion/styled";
import { css } from "@emotion/react";
import { space } from "sentry/styles/space";

// ✅ Don't be afraid of inline styles for one-off values
const Grid = styled("div")`
  display: grid;
  grid-template-areas:
    "logo search"
    "sidebar main";

  gap: var(--grid-gap, 0px);

  @media (max-width: ${(p) => p.theme.breakpoints.small}) {
    grid-template:
      "logo" auto
      "search" auto
      "main" auto / 1fr;

    /* Hide sidebar, which is in an auto-column */
    grid-auto-columns: 0px;
  }
`;

return (
  <Grid
    css={css`
      --grid-gap: ${space(1)};
    `}
  >
    <HomeLink style={{ gridArea: "logo" }} />
    <SearchForm style={{ gridArea: "search" }} />
    <Navigation style={{ gridArea: "sidebar" }} />
    <DataTable style={{ gridArea: "main" }} />
  </Grid>
);

For values that change frequently it's beneficial to use a component ref and set the values imperatively. This can be done without triggering react which can result in much more efficient updates.

Note that you can set CSS values directly, or if the component is reading a CSS variable, call elem.style.setProperty('--foo', 'value') instead.

Copied
import styled from "@emotion/styled";
import { useRef } from "react";

const Area = styled("div")`
  width: 500px;
  height: 500px;
  position: relative;
`;
const MovingCircle = styled("div")`
  position: absolute;
  width: 50px;
  height: 50px;
  background: black;
  border-radius: 50%;
`;

const ref = useRef<HTMLDivElement>(null);

return (
  <Area
    onMouseMove={(event) => {
      if (!ref.current) {
        return;
      }
      // ✅ Imperatively set values that change frequently to avoid react re-renders
      ref.current.style.top = event.clientY + "px";
      ref.current.style.left = event.clientX + "px";
    }}
  >
    <MovingCircle ref={ref} />
  </Area>
);

This happens when you use a styled component as a selector, we need to tell stylelint that what we are interpolating is a selector by using comments to assist the linter. e.g.

Copied
const ButtonBar = styled("div")`
  ${/* sc-selector */Button) {
     border-radius: 0;
  }
`;

See https://styled-components.com/docs/tooling#interpolation-tagging for other tags and more information.

Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").