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.

Basics

To use Style Components pull in styled and apply your CSS. For consistent spacing you should also use space():

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

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;
`;

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

Theme

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};
`;

Dynamic Properties

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.

Avoid Dynamic Props

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.

Use Data Selectors

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>;

Use Style & CSS Attributes

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';

// ✅ 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>
);

Imperatively Set Styles

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>
);

stylelint Errors

"No duplicate selectors"

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.

You can edit this page on GitHub.