circuit

The Modern Way to Style with Styled Components

An in-depth look at the power of Styled Components


My Journey

Like many, I first started styling React with inline CSS. It was an adjustment coming from stylesheets but I really liked the ability to define localized styles and leverage the power of Javascript. Of course, there were issues such as performance and lack of pseudo-selector support, but overall I was happy with it and there was no compelling reason or deficiency to search for another way to style my components.

When I was first introduced to Styled Components, I was a bit apprehensive of the benefits, having gotten used to inline CSS for so long. After all, React documentation (at the time), still used inline CSS for all their examples. As I prototyped the framework, I discovered a best of both worlds between inline CSS and traditional stylesheets.

Styled Components

While defined very differently, Styled Components act fundamentally like React components. They both take props as input and output HTML.

Let’s take this sample button component styled similarly to Bootstrap’s buttons with Styled Components.

import styled from 'styled-components';

const Button = styled.button`
  display: inline-block;
  padding: 6px 12px;
  font-size: 16px;
  font-family: Arial, sans-serif;
  line-height: 1.5;
  color: white;
  background-color: #6c757d;
  border: none;
  border-radius: 4px;

  :not(:disabled) {
    cursor: pointer;
  }

  :hover {
    background-color: #5a6268;
  }
`;

The styled component factory is imported and used to generate a button with styles using a template literal (...). This factory supports all valid HTML elements whether that be div, h1, p, etc. Similarly, the styles in the template literal accepts all valid CSS so you can go crazy with complex CSS selectors.

Usage

Once defined, they act exactly like React components and are used in the exact same way — JSX. After all, Styled Components are simply React components with some styling.

const App = () => {
  return (
    <Button onClick={() => alert('clicked!')} type="button">
      Button
    </Button>
  );
};

Styled Component ButtonStyled Component Button

Just like React components, Styled Components also accepts props and are defined in the same exact way. Styled Components will pass all (valid) DOM properties to the DOM.

Internals

Okay, so how does this work? What wizardly process is happening internally?

  1. At render, Styled Components will generate a unique classname based on the defined styles.

  2. Styled Components will inject a style tag (

  3. Styled Components will render the element with the unique classname.

These steps will result in the DOM structure looking like this for our example.

<!DOCTYPE html>
<html lang="en">
  <head>
    <style data-styled="active" data-styled-version="5.2.0">
      .bAeiQF {
          display: inline-block;
          padding: 6px 12px;
          font-size: 16px;
          font-family: Arial, sans-serif;
          line-height: 1.5;
          color: white;
          background-color: #6c757d;
          border: none;
          border-radius: 4px;
      }
      .bAeiQF:not(:disabled) {
          cursor: pointer;
      }
      .bAeiQF:hover {
          background-color: #5a6268;
      }
    </style>
  </head>
  <body>
  <div id="root">
    <button type="button" class="sc-bdnylx bPvsWA">
      Button
    </button>
  </div>
  </body>
</html>

Extending Components

The styled component factory can also be used to extend existing styles. It can take a Styled Component, and return a new one in a manner reminiscent of React’s higher order components.

const PrimaryButton = styled(Button)`
  background-color: #007bff;
  :hover {
    background-color: #0069d9;
  }
`;

const App = () => {
  return (
    <PrimaryButton onClick={() => alert('clicked!')} type="button">
      Primary
    </Button>
  );
};

Extended Style ButtonExtended Style Button

Using styled as a function will produce a new Styled Component that has the styles from the Button component and the new styles defined in the template literal. If styles conflict, the new styles will take precedence. All props (except for those with $ prefixes; see Styling Props) will also be passed down to the underlying component.

Similarly, The styled component factory can extend React components with a small modification.

const ButtonComponent = ({ className }) => {
  return (
    <Button
      className={className}
      onClick={() => alert('clicked!')}
      type="button"
    >
      Primary
    </Button>
  );
};

const PrimaryButton = styled(ButtonComponent)`
  background-color: #007bff;
  :hover {
    background-color: #0069d9;
  }
`;

The one key additional addition that enables this is the className prop. The extended Styled Component will set the className and pass it to the React Component when rendered. This className will map to the defined style just like how it works with Styled Components. So, without className, the style will not get applied.

Composable Styles

Styled Components also allows us to define reusable styles that can be composed into Styled Components.

import styled, { css } from 'styled-components';

const blackFont = css`
  color: black;
`;

const WarningButton = styled(Button)`
  background-color: #ffc107;
  :hover {
    background-color: #e0a800;
  }
  ${blackFont}
`;

const App = () => {
  return (
    <WarningButton onClick={() => alert('clicked!')} type="button">
      Warning
    </WarningButton>
  );
};

Composed Style ButtonComposed Style Button

Here, blackFont is a generic style. It’s defined with a template literal using the css helper. The WarningButton and any other Styled Component can include this style with the dollar sign curly braces syntax to merge the two template literals.

Styling Props

In addition, Styled Components themselves can use props to modify styling.

import styled, { css } from 'styled-components';

const SuccessButton = styled(Button)`
  ${props =>
    props.$success
      ? css`
          background-color: #28a745;
          :hover {
            background-color: #218838;
          }
        `
      : ''}
`;

const App = () => {
  return (
    <SuccessButton
      $success
      onClick={() => alert('clicked!')}
      type="button"
    >
        Success
    </SuccessButton>
  );
};

Styling Props ButtonStyling Props Button

Here, the Button component is extended to take a $success prop which will conditionally set the the success styles (using a composable style) if set to true.

Though styling props do not need the $ prefix, I highly recommend it. Normally, all props are passed to the underlying component (DOM element, React Component, or Styled Component). Props with names that are prefixed with $, called transient props, are only used by the defined Styled Component and are not passed down to the underlying component. This makes it crystal clear which props are for styling and which props are for the underlying component.

Final Thoughts

Styled Components provides a simple and fantastic interface for styling. It enhances the developer experience for CSS using core React principles. It integrates so well with React that I can’t help wonder why React doesn’t just natively integrate it — it’s just that good. I will never go back to my old ways of styling.

Resources




Continue Learning