DEV Community

loading...
Cover image for Using CSS Variables to Tame Styled Component Props

Using CSS Variables to Tame Styled Component Props

arnonate profile image Nate Arnold Updated on ・4 min read

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 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 package without having to declare individual classes for every prop-related style update. The following is an example of a button.

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

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

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

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

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

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.

Discussion

pic
Editor guide
Collapse
yaireo profile image
Yair Even Or

This approach still creates duplicate CSS for every prop change...with duplicate variables.. this saves nothing.

A better approach would be to use style attribute to do the variables change:
dev.to/terrierscript/tips-css-vari...

Here's a screenshot that shows this folly:

monosnap.com/file/JE19V30V2mfkuCci...

Every time you are using props in Styled-Components you are creating another a complete clone of all the defined styles plus the change. Tons of duplicate code for nothing, this is the opposite what the logic to use CSS variables instead of props!