Using CSS Variables to Tame Styled Component Props
Tags: react, css, javascript, styledcomponents
Published: 2020-06-09 01:46:19

When I started writing React components about 3 years ago our code base was full of SCSS. This quickly became unmanageable, not because of SCSS, but as a result of all the styles being overwritten in new components as I was converting an Angular library. At the time, afaik(new), there were only 2 ways to keep your styles isolated and scoped in React: Style objects in JS and CSS Modules. The ecosystem has evolved a lot in 3 years. Today we have a lot of options for scoped styles in React. I have leaned towards [`styled-components`](https://styled-components.com/) for a majority of projects to date. 

There are a lot of things I love about this library. From the SCSS inspired syntax to the ability to create and inherit styles, this library is a solid solution for scoping styles to components and implementing it "feels" good to me.

One of my favorite features of `styled-components` is the ability to pass in props and leverage these props to manipulate styles. It is a lot like using the [`classnames`](https://jedwatson.github.io/classnames/) package without having to declare individual classes for every prop-related style update. The following is an example of a button.

```javascript
<Button>Click Me!</Button>

const Button = styled.button`
  background-color: gray;
`;
```

In this example the default `Button` color is `gray`. Traditionally, if we wanted to update the background color for a `Primary` variant of `Button` we would need to add a `className` to the `Button` in order to manipulate the style or pass the `background-color` override in as a style update on the Button in our jsx.

```javascript
<Button className="primary">Click Me!</Button>

const Button = styled.button`
  background-color: gray;

  &.primary {
    background-color: blue;
  }
`;
```

As you can see, `styled-components` provides a way for us to add variant classes without the need for the `classnames` package, but there is a better way to handle this built into the `styled` API. We can manipulate variants based on props that are passed to our `Button`! Take a look at the following:

```javascript
<Button variant="primary">Click Me!</Button>

const Button = styled.button`
  background-color: {$props => props.variant === "primary" ? "blue": "gray"};
`;
```

Now, with a little ternary action, we can actually toggle the color of our button based on the value passed into the `variant` prop. Cool, right? This is one of the features that makes `styled-components` feel so React-like.

Normally, this approach is fine, but when we start adding more props-based styles into the mix, our `Button` can get busy and the variants can be hard to grok when we come back to it for future iterations. Like such:

```javascript
<Button
  variant="primary"
  shape="rounded"
  weight="bold"
  size="large"
>Click Me!</Button>

const Button = styled.button`
  background-color: {$props => props.variant === "primary" ? "blue": "gray"};
  color: {$props => props.variant === "primary" ? "white": "charcoal"};
  border-radius: {$props => props.shape === "rounded" ? "8px": "0"};
  font-weight: {$props => props.weight === "bold" ? "700": "400"};
  font-size: {$props => props.size === "large" ? "18px": "12px"};
  padding: {$props => props.size === "large" ? "1rem": "0.5rem"};
`;
```

As you can see, all the variations on our button quickly get lost in the stack of ternary operations inside of our styles block. Not only this, but without adding in a type-checking library it is hard to follow which props we are actually expecting in our `styled.button`. If we wanted to update only the font-weight of a `bold` button, we would have to sift through this mess of ternaries. If we wanted to add a 3rd option for font-weight it would get even messier.

**Enter CSS Variables.**

Fortunately, CSS variables are supported by styled components and can easily be inserted into our styles block to ensure our intentions are clear to the next developer that inherits our button code (or our future selves). Take a look at the `styled.button` when we apply CSS Variables to every prop option.

```javascript
<Button
  variant="primary"
  shape="rounded"
  weight="bold"
  size="large"
>Click Me!</Button>

const Button = styled.button`
  --props-variant-default-background-color: gray;
  --props-variant-default-color: charcoal;
  --props-variant-primary-background-color: blue;
  --props-variant-primary-color: white;
  --props-variant-primary: blue;
  --props-shape-default: 0;
  --props-shape-rounded: 8px;
  --props-weight-default: 400;
  --props-weight-bold: 700;
  --props-size-default-size: 12px;
  --props-size-large-size: 18px;
  --props-size-default-padding: 0.5rem;
  --props-size-large-padding: 1rem;


  background-color: {$props =>
    props.variant === "primary" ?
    "var(--props-variant-primary-background-color)" :
    "var(--props-variant-default-background-color)"};
  color: {$props =>
    props.variant === "primary" ?
    "var(--props-variant-primary-color)" :
    "var(--props-variant-default-color)"};
  border-radius: {$props =>
    props.shape === "rounded" ?
    "var(--props-shape-rounded)" :
    "var(--props-shape-default)"};
  font-weight: {$props =>
    props.weight === "bold" ?
    "var(--props-weight-bold)" :
    "var(--props-weight-default)"};
  font-size: {$props =>
     props.size === "large" ?
     "var(--props-size-large-size)" :
     "var(--props-size-default-size)"};
  padding: {$props =>
     props.size === "large" ?
     "var(--props-size-large-padding)" :
     "var(--props-size-default-padding)"};
`;
```

Alright, I know, this approach is more verbose for sure. It will take you more lines of code than the original implementation. However, your future self will thank you, because there is no need for guessing or fishing through ternaries or switch statements. It is very obvious where I go to update the font size of a `large` variant to `20px`. It is also clear what props we are expecting.

We can also use CSS Variables to toggle properties inside of media queries:

```javascript
const Button = styled.button`
  --props-size-default: 12px;
  --props-size-large: 18px;
  
  @media screen and (min-width: 860px) {
    --props-size-default: 14px;
    --props-size-large: 20px;
  }

  font-size: {$props =>
     props.size === "large" ?
     "var(--props-size-large)" :
     "var(--props-size-default)"};
`;
```

That is all! Have fun adding CSS Variables to your Styled Components! Let me know any other ways you have incorporated CSS Variables into your React workflow.