Writing responsive React components that scale

Vitor Dino
14islands
Published in
6 min readAug 14, 2020

--

One of the main challenges of building any larger responsive website or web app with multiple user flows and countless unique screens is to make it scalable.

This article is specifically about translating a design specification into reusable components, with a focus on code reuse, composition and readability.

Brief context and why are we still discussing this today.

Since the dawn of time, developers have struggled with writing CSS, and dozens of methodologies have been created to circumvent the problem of class names clash. Multiple pre-processors have taken the burden of solving the problem of repetition and nesting.

Later, already on React land, css modules killed the class name clashing problem and helped to crystalize the idea of a component — an element that survives by itself, both within markup and styles.

Even with solid methodologies and that first set of tools in place, many problems still survived, like the relation between components and edge cases that required writing even more CSS. The last barrier keeps on the communication between markup and style, limited by passing class names and rendering style that was already written.

The popularization of css-in-js techniques and libraries between many things allowed us to share the same variables, keeping visual state consistent and easier with only props, and cleared all the clutter of managing class names — even though they would still be generated dynamically by css modules.

But a tool is just a tool. I once heard something like this:

  • Give a Neanderthal a pencil, and he’ll ask “That’s it?” But a small piece of graphite allowed humankind to begin to keep historical records across generations.

Today we can look at plain CSS as like rock drawings of cavemen. It’s a rudimentary medium with some problems, but it led the way, and the idea behind CSS is still alive. Yet, only having nice tools won’t solve all problems instantly. This is only obvious when we look back.

photo of a cave opening, with shining light coming in
by Bruno van der Kraan

Beyond the tools.

One way to tackle some of these problems is by using design tokens — colors, spacings, typographic scales and animation/transition settings — that can be used across the project and, now, inside both CSS and JS code. CSS preprocessors, like sass and stylus, had variables long ago, and now even the native CSS custom properties are gaining traction, But they do fail on the sharing paradigm with our JS code and markup.

Creating our own components with the plain styled syntax was pretty ok and solved most of my issues, but reusing them in a different context on different screens required some in-place responsive customization. That felt a little bit unintuitive, and I often had to compose into another slightly different-styled component every place.

The main libraries that came to solve this were rebass, styled-system and now theme-ui, and they are definitely spreading the ideas of using design tokens on a theme, writing less CSS, and having it super easy to swap styles in place.

These libraries follow the core rules of a React component: they are composable, they are simple, and they are declarative. They are impressively good on what they propose, they are attracting more and more people toward their simple mindset, and I found it truly amazing when I saw it for the first time.

<Flex
alignItems='center'
px={3}
py={4}
bg='muted'
>
<Heading>Hello</Heading>
<Box mx='auto' />
<Button>Beep</Button>
<Button ml={2} variant='secondary'>Boop</Button>
</Flex>

It clearly draws some inspiration from Functional CSS methodologies and libraries like tachyons and tailwind.

Most of the time, this degree of freedom starts degrading the scalability of our app. To write markup the first time is fast. But it becomes pretty tiresome for major refactors and overall maintenance. It turns out that using Functional CSS don’t enforce any code reuse or good practice. In the end, it’s just a tool without an opinion on how you and your team use it.

And that’s good. We need some structural, unopinionated tools. After all, how could we imagine a Wacom tablet without a good pencil?

by Florian Klauer

As you grow in ways you’re using tools, you start losing control of the finer points. You probably even become more proficient at using them, but it also becomes a hassle to maintain consistency, and the cost for refactoring the app gets exponential.

The first thing I realized when I looked at more complex rebass code after a while was that I didn’t exactly know what each of the components were specifically doing. I had to inspect each one closely, and they almost seemed like a riddle I had to solve, and that was really exhausting.

Using all these approaches and facing the different advantages and problems created by them inspired me to pursue a better way on how to style things on React.

Be human, be readable, be clear

Taking a step back — way back — to simpler bootstrap times, I’ve saw one big pattern that some seem to have forgotten:

<div class="row">
<div class="col-xs-12 col-sm-6 col-lg-4">
<p>First</p>
</div>
<div class="col-xs-12 col-sm-6 col-lg-4">
<p>Second</p>
</div>
</div>

This is how we used to lay grids. Although not as powerful as today’s standard, it served its purpose, was fairly readable and had a single purpose. At the time, this brought many advantages:

  1. We didn’t need to know what was going down the surface — probably some scary float positioning hacks.
  2. It was pretty reliable and you could easily reuse it everywhere on your project, with a single line.
  3. It even stayed the same across the years, even across different major versions.

The key difference Bootstrap brought to us the introduction of an API for solving a specific layout problem, and a quite difficult one when it launched. Maybe the bootstrap grid wasn’t the first “CSS API” out there, but certainly was one of the most used, copied and worked upon over time.

From this inspiration we can clearly see what’s lacking on a pure rebass approach for the same result:

const GridWithRebass = () => (
<Flex mx={-2}>
<Box width={[1, 1/2, 1/3]} px={2}>
<p>First</p>
</Box>
<Box width={[1, 1/2, 1/3]} px={2}>
<p>Second</p>
</Box>
</Flex>
)

It gets all the benefits of css-in-js but none of that old-school Bootsrap CSS API. What if we pick the best of both worlds and start using more components with clear intents and fewer props? This would allow us to integrate design tokens into them and create some nice APIs again, so that when using them, we wouldn’t care so much about the underlaying details.

It would probably look a little like this:

<Grid.Row>
<Grid.Column xs={12} sm={6} lg={4}>
<p>First</p>
</Grid.Column>
<Grid.Column xs={12} sm={6} lg={4}>
<p>Second</p>
</Grid.Column>
</Grid.Row>

On this grid example we achieved the same readability and result of the bootstrap one, so we brought the benefits from the past to today’s modern stack. Obviously, we can extend this idea to basically anything that might involve breakpoints, and even mix that with our design tokens and create nice APIs to use across the project.

Following these ideas, we can get faster development of really creative layouts with simpler props, and better understanding around what we were doing it.

How to achieve it, the easier way.

As usual, there are infinite ways of implementing these patterns. I began doing it by hand for a bunch of components, writing some in-place utilities that would fulfill the needs of each until I could see the bigger picture. After that, I could separate 2 simple functions that allowed me to create this components more easily, they are available inside the etymos package, alongside with some other cool utilities.

But of course there are plenty of ways to implement it, so feel free to build upon this idea and release your own toolset.

--

--