The Advanced Way to Style with Styled Components

A more in-depth look at the power of styled-components

By Michael Chang

September 16th, 2020

image

My Journey

I love Styled Components. It makes styling in React so simple.

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

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

Styled Components ButtonStyled Components Button

After learning the basics, I set about migrating my codebase to Styled Components. In doing so, I gradually uncovered some issues and inefficiencies. There were little things here and there that I wanted to do, but couldn’t, with the knowledge I had. Many of these, I solved in roundabout ways so they weren’t major issues. But, it was enough for me to dive back into the Styled Components documentation. Luckily for me, the contributors of Styled Components had already figured them out.

Style Objects

Sometimes we are forced to use style objects via the style prop. This could be because the codebase is only partially migrated to Styled Components or because some third party library uses it, something quite common with hooks-based libraries such as React Dropzone or React Table. Styled Components can merge these style objects into Styled Component styling.

const StylePropButton = styled(Button)`
  ${props => props.$style ?? {}}
`;

const App = () => {
  return (
    <StylePropButton
      $style={{ backgroundColor: '#007bff' }}
      onClick={() => alert('clicked!')}
      type="button"
    >
      Primary
    </StylePropButton>
  );
};

Style Prop ButtonStyle Prop Button

This component accepts an inline style object that is then merged into the Styled Components styles by simply returning the object into the template literal. The result is a seamless integration between two drastically different ways of styling React.

Note the I’ve named the prop $style instead of style. This prevents the styles from being applied twice. If the prop name was style, the button would be styled via inline styles as well since Styled Components passes props to the underlying 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.

So why do it this way? You could just use the style prop directly and not merge the the style object into the Styled Components styles. The main advantage is dealing with specificity. Inline styles will always be at a higher priority than external CSS through Styled Components. This method will prevent the use of inline styles and instead convert them into external CSS via Styled Components. Then, you can easily override those styles as you see fit, without the need for !important.

Dynamic Components

Sometimes, you want to apply the same styles, but to a different underlying component (DOM element, React Component, or Styled Component). You could define a composable style with css and create a new Styled Component with that composable style. But there’s an easier way! Styled Components come with a special as prop that will allow you to redefine the component rendered with the same style.

const App = () => {
  return (
    <Button as="a" href="https://styled-components.com/">
      Link
    </Button>
  );
};

This will render the following to the DOM.

<a
  href="[https://styled-components.com/](https://styled-components.com/)"
  class="sc-bdnylx bPvsWA"
>
  Link
</a>

Just like that, with a single prop, you’ve rendered an anchor tag instead of a button with the same styles as Button!

Attributes

Sometimes certain props are repeated over and over again. In our example, type is almost always button so it would be helpful to not have to define this every time. This issue can be solved by defining a React component Button that returns the Styled Component Button with the type prop defaulted. But, like before, there’s a native Styled Components method for accomplishing this.

const AttributeButton = styled(Button).attrs(props => {
  const { type = 'button' } = props;
  return { type };
})`...`;

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

The Styled Components attrs function allows you to define additional props the Styled Component will have without needing to explicitly define it upon usage. In this case, type will be defaulted to button if not set.

The attrs function can also accept an object for static props. However, if defined this way, the value cannot be overwritten by the user as the attributes take precedent.

PropTypes

Since Styled Components are effectively React components, they also support propTypes and defaultProps!

const PropTypesButton = styled(Button)`...`;
PropTypesButton.propTypes = {
  onClick: PropTypes.func.isRequired,
  type: PropTypes.oneOf(['button', 'submit', 'reset']),
};
PropTypesButton.defaultProps = {
  type: 'button',
};

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

Just like with React component, this will help validate prop data and warn if invalid for Styled Components.

Global Stylesheet

While migrating my codebase to Styled Components, I still imported a CSS stylesheet for my global styles. This was one of the last non Styled Components styling left in my codebase and as someone who loves consistency, I had to find a replacement. Turns out, Styled Components supports global styles using createGlobalStyle.

import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  *, *::before, *::after {
    box-sizing: border-box;
  }

  a {
    text-decoration: none;
  }
`;

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

Problem solved. Note that, unlike regular Styled Components, the global style does not accept children. It is a global style after all.

Babel Integration

One of the best advantages through styling via Styled Components is the localization of styles through some unique, autogenerated classnames. This, however, does introduce an unfortunate side effect — in a complex app completely styled through Styled Components, it’s difficult to know which DOM element is generated by which line of JSX. Fortunately, Styled Components provides a babel plugin called babel-plugin-styled-components. This plugin will add a developer friendly classname to each of your Styled Components using the file name and the Styled Component variable name, allowing you to easily track down your component.

<button
  type="button"
  class="App__Button-sc-1r1t5ph-0 iFtKFk"
>
  Button
</button>

To enable this plugin, simply add it to your babel.config.js.

module.exports = {
  presets: [],
  plugins: ['babel-plugin-styled-components'],
};

For codebases built off of zero-config projects such as the Create React App, Styled Components also provides a babel macro that will achieve the same result.

import styled from 'styled-components/macro';

const Button = styled.button`...`;

Jest Integration

For testing, Styled Components also has a Jest plugin called jest-styled-components. This is incredibly useful as the unique autogenerated classnames makes it incredibly easy for snapshot tests to fail. With this plugin, predictable classnames are created along with the CSS styles themselves.

*// Button.test.js*
import React from 'react';
import { render } from '@testing-library/react';
import 'jest-styled-components';

import Button from '../Button';

test('snapshot', () => {
  const { container } = render(<Button />);
  expect(container.firstChild).toMatchSnapshot();
});

*// __snapshots__/Button.test.js.snap*
exports[`snapshot 1`] = `
.c0 {
  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;
}

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

.c0:hover {
  background-color: #5a6268;
}

<button
  class="c0"
/>
`;

If snapshots are not your thing and prefer to test for individual styles directly, jest-styled-components extends Jest’s expect with the toHaveStyleRule matcher so you can manually check whether an element has the expected styles.

test('should have style', () => {
  const { container } = render(<Button />);
  expect(container.firstChild).toHaveStyleRule(
    'background-color',
    '#6c757d'
  );
  expect(container.firstChild).toHaveStyleRule(
    'background-color',
    '#5a6268',
    {
      modifier: ':hover',
    }
  );
});

Stylelint Integration

And finally, linting. Styled Components provides several libraries to integrate with stylelint, ensuring that you write in the best CSS standards. stylelint-processor-styled-components extracts styles from Styled Components for stylelint to lint and stylelint-config-styled-components disables the stylelint rules that conflict with Styled Components. To integrate, add these two libraries to your stylelint.config.js.

module.exports = {
  extends: ['stylelint-config-styled-components'],
  processors: ['stylelint-processor-styled-components'],
};

To run, add the following to your package.json and run npm run test:css.

{
  "scripts": {
    "lint:css": "stylelint './src/**/*.js'",
  },
}

Final Thoughts

And there you have it — everything you wanted to know about Styled Components and more! It provides styling solutions for all sorts of scenarios that you didn’t even know you will face. It provides integrations with the common tools in the React ecosystem. It provides a mature styling framework that simplifies and enhances the developer experience. Now that you know, start using Styled Components like an expert!

Resources



Continue Learning