How to leverage styled components and css variables to build truly reusable components in React

Innocent Amadi
Facebook Developer Circles Lagos
8 min readApr 19, 2018

--

tl;dr:

  • A truly reusable UI component requires no changes to adapt itself to its environment.
  • CSS variables are a way to set variables to be used in CSS stylesheets.
  • Styled Components is a library that allows you add CSS styles to a React component itself

What makes a truly reusable component?

The word ‘reusable’ is used a lot in component-based design. The idea of writing a component once, then using it anywhere is efficient and follows the ‘Don’t Repeat Yourself’ principle.

Typically at the beginning of a project we create shared components like buttons and panels. Let’s look at how a button is typically defined.

.btn {
color: tomato;
background-color: #eee;
border: 2px solid tomato;
letter-spacing: 1px;
font-weight: 700;
border-radius: 5px;
font-size: 1.8rem;
padding: 10px 15px;
};

Then we create variants of the same button.

.btn.large {
padding: 20px 30px;
font-size: 3rem;
}
.btn.small {
padding: 5px 10px;
font-size: 1.4rem;
}

The button component end up becoming something like this:

const button => ({ styles }) =>
<button class={`btn ${styles}`}>
Awesome button
</button>

Now you can add other CSS classes to the button without stress.

Everything is going smoothly. Then the project requires you to make a promotional banner. The call-to-action on it should be large but with a bit less padding. Also the color should be aqua green. No problem. You whip up your editor and write:

.promotion .btn {
padding: 15px 20px;
color: aquagreen;
background-color: white;
}

You have a nice promotion button. The Product Manger calls. This promotion button is to be used in two banners, one would require the inverted version of your button. Piece of cake. Editor out.

.promotion .btn.inverted {
color: white;
background-color: aquagreen;
}

The inverted banner is to be used in the sidebar bar and the header. The sidebar button should almost cover the width of the sidebar. You end up with a button component that looks like:

<button class=“sidebar promotion btn inverted large”>
Awesome button
</button>

Play this a few months forward. It quickly gets to a time you have no idea what else would break if you tweak the .promotion.btn property even the slightest.

To get around this developers add classes, rarely change and almost never subtract. Very quickly a simple button becomes host to a long list of classes. See how CSS often becomes a nightmare to maintain years into a project?

Let’s get back to the button. Can we say the button component itself is truly reusable?

A truly reusable UI component will require no changes to the component itself to adapt it to its surroundings.

You should be able to literally copy and paste the button code from sidebar to header and it just … works!

To achieve this level of adaptability, we’ll build on principles from Glen Maddern’s 2016 talk on The future of reusable CSS.

An element shouldn’t set its width, margin, height and color. These attributes should be set by its parent(s).

This concept isn’t new in itself. At the time of the talk however it wasn’t very practical to me for two reasons:

  1. CSS variables weren’t widely supported across browsers. Polyfilling just didn’t work.
  2. Adding a ‘style’ prop to pass on width, margin, height and color stops being a very attractive proposition when it has to be done for every adaptable component, every time.

It’s 2018. CSS variables boasts of over 80% browser adoption. Add styled components to the mix and we have a truly adaptable component.

What are CSS variables?

From Mozilla docs:

CSS variables are entities defined by CSS authors that contain specific values to be reused throughout a document. They are set using custom property notation (e.g. — main-color: black;) and are accessed using the var() function (e.g., color: var( — main-color);) .

To keep things simple, see css variables as similar to good old SASS variables written with a ‘ — ’ prefix to let CSS know it is a variable. Let’s set a CSS variable — primary-color on the promotion banner and define a border with it.

.promotion {
--primary-color: aqua green;
--bg-color: white;
/* some other style rules*/
border: 1px solid var(—primary-color);
}

Yeah, boring right? Hey, remember our guidelines for building reusable components? An element shouldn’t set its own width, margin, height and color. Implementing this guideline is where CSS variables really begin to shine. Let’s modify the button style rules to obey these principles.

.btn {
/* set fallback defaults in case the CSS variables aren't set */
--btn-fg: var(--primary-color, tomato);
--btn-bg: var(--bg-color, #eee);
--btn-font-size: var(--font-size, 18px);
color: var(--btn-fg);
background-color: var(--btn-bg);
border: 2px solid var(--btn-fg);
letter-spacing: 1px;
font-weight: 700;
border-radius: 5px;
font-size: var(--btn-font-size);
padding: 10px 15px;
}

Here we set the color to expect the primary-color variable to be set from a parent element (or grandparent, somewhere up the hierarchy tree). When setting the button CSS variables, we add default values just in case a variable is never set by a parent. Adopting this ‘fallback’ practice allows you make as many properties as you want adaptable to parent elements while still being able to use them on their own.

With our CSS variables approach, we can do away with defining multiple variations of buttons. The parent would control the button’s look and feel.

What are styled components?

If you have no idea what styled components are, it’s okay to do some reading up on their website before coming back here.

Styled Components is a library that allows you add CSS styles to a React component itself. I’m sure it’s used in other libraries, but this post covers React usage. The style rules are written as close to CSS as possible. Styled components even allows SASS-like nesting.

To convert our button to a styled component, copy the rules defined as .btn, then modify the button component:

import React from 'react'
import styled from 'styled-components'
const StyledButton = styled.button`
color: var( — primary-color);
background-color: #var( — bg-color, #eee);
border: 2px solid var( — primary-color);
letter-spacing: 1px;
font-weight: 700;
border-radius: 5px;
font-size: var( — btn-font-size, 18px);
padding: 10px 15px;
`
export const Button = (props) => <StyledButton {…props} />

So what’s the big deal? How is it different from me declaring inline styles?

  1. We do away with style names. Remember the nightmarish style names problems we explained at the beginning of this article? Styled-components take care of making sure your class names are unique by creating a hash out of the style rules themselves. You never have to worry about what class names your component ends up having.
  2. We retain the succinctness of writing css. Writing backgroundColor with react stylesheet never felt natural anyways 🙂
  3. We retain the ability to use CSS variables. CSS variables still work down the components hierarchy.
  4. We write SASS-like CSS. It might be difficult to completely do away with classes when working with CSS. I see this as a good thing though. The CSS problem have never been with the classes declared on bootstrap or materialize, but arise over time as developers continue to target, modify and add to these classes. With styled-components you can still target classes hierarchically as you would do in SASS. This lets you modify the look and feel of children elements but in a scoped domain, so damage is very easily managed.

How do we use CSS variables with styled components to make truly reusable components?

To make for good contrast, let apply button styles using StyleSheet.create({}) and then write the button in a way that we can pass in inline styles to override the default.

import React from 'react'export const PromoBanner = () => {
const btnStyles = {
color: acquagreen,
border: 1px solid acquagreen,
backgroundColor: white,
fontSize: 18px
}
return (
<div class=“promotion”>
<Button styles={btnStyles}
btnText=“Buy now”/>
</div>
)
}
export const Button = ({ styles, btnText }) =>
<Button style= {styles}>{btnText}</Button>

This is completely valid since the ability to write reusable components lie more in where the rules are applied than how. Major advantages styled components has over React StyleSheets are the ability to write CSS-like, sass compliant rules and the ability to use css variables. Notice we had to set color and border with the same color separately? Also if the button is not directly rendered under promo (a grand or great-grand child) then we would have to keep passing down the btnStyles props until the button gets it.

Let’s modify our promotion panel to use our improved styled component button leveraging its css variables.

import React from ‘react’
import Button from ‘./Button’
import styled from ‘styled-components’
const StyledPromoBanner = styled.div`
--primary-color: aquagreen;
--bg-color: #eee;
--btn-font-size: 18px;
/* other style rules */
`
export const PromoBanner = () =>
<StyledPromoBanner>
/* other components */
<Button>Buy now</Button>
</StyledPromoBanner>

See what’s going on here? We’re able to reuse our button without directly modifying the button itself. It reads like basic CSS, we don’t need to pass styles around and we can target specific variables the button will use. This allows us make use of the button without any modification to the button itself, making it a truly reusable component in this sense.

What could improve in future?

  1. As at the time of writing this, styled components have no support for lighten() or darken() SASS functions that are used to tweak a specific color in a way that still returns a complementary color. You could use the polished library to achieve the same functionality though.
  2. CSS variables aren’t yet supported in Opera Mini and Samsung Internet v4 (version 6.2 fully supports it though). So if you care a lot about your Opera Mini users, you should definitely include regular style rules as fallbacks when using CSS variables. Also Microsoft Edge 15 doesn’t evaluate nested calculations for css variables and crashes when animations use css variables. The good news is the Edge issues have already been fixed for newer versions of Edge.

Edit: Max Stoiber, one of the creators/maintainers of styled-components just told me polished.js is actually the official solution for SASS-style helpers like lighten() and darken() .

The bigger picture.

We already see this design concept scale up to entire systems in Microservice architecture. The guiding principle I use is to build the component or system in a way that it could be broken off into a stand-alone library or Microservice very easily if we choose to.

Admittedly this doesn’t play too well with the current extremely popular MVC architecture. However the fact that we’ve been able to adopt it successfully in the frontend tells us it is already happening.

As component-based design continues to gain on page-based design on the frontend, we would continue to see better ways to make components more reusable.

Conclusion

Hopefully huge monstrous CSS files would become a thing of the past, along with confusing class names that become difficult to manage.

Have you tried out styled-components, CSS variables or a combination of both? Is there an angle I might have missed? I’m looking forward to reading what your experience was, what you learnt, what you loved and what you didn’t.

Further reads

Glen Maddern: The Future of Reusable CSS

Glen Maddern: The Road to Styled Components: CSS in component-based systems

Alan B. Smith: Why we use styled components at Decisiv

Thanks to the Developer Circles Lagos Writers’ Corner for the proof-reads.

If you enjoyed reading this, help others find it too by clicking on the 💚. Thank you! 🙂

--

--

Innocent Amadi
Facebook Developer Circles Lagos

Software Engineer at Andela and HappyMoney, Lead at Facebook Developers Circle Lagos and Teencode Africa. @tru2cent on twitter