Build a Reusable Design System With React

React has done a lot to simplify web development. React's component-based architecture makes it easy in principle to decompose and reuse code. However, it's not always clear for developers how to share their components across projects. In this post, I'll show you some ways to fix that.

React has made it easier to write beautiful, expressive code. However, without clear patterns for component reuse, code becomes divergent over time and becomes very difficult to maintain. I've seen codebases where the same UI element had ten different implementations! Another issue is that, more often than not, developers tend to couple the UI and the business functionality too tightly and struggle later when the UI changes.

Today, we'll see how we can create shareable UI components and how to establish a consistent design language across your application.

If you want to know more about design systems and how they work, start by watching our free course with Adi Purdila.

Getting Started

You need an empty React project to begin. The quickest way to do this is through create-react-app, but it takes some effort to set up Sass with this. I've created a skeleton app, which you can clone from GitHub. You can also find the final project in our tutorial GitHub repo.

To run, do a yarn-install to pull all dependencies in, and then run yarn start to bring up the application.

All the visual components will reside under the design_system folder along with the corresponding styles. Any global styles or variables will be under src/styles.

Setting Up the Design Baseline

When was the last time you got a you-are-dead-to-me look from your design peers, for getting the padding wrong by half a pixel, or not being able to differentiate between various shades of grey? (There is a difference between #eee and #efefef, I'm told, and I intend to find it out one of these days.)

One of the aims of building a UI library is to improve the relationship between the design and development team. Front-end developers have been coordinating with API designers for a while now and are good at establishing API contracts. But for some reason, it eludes us while coordinating with the design team. If you think about it, there are only a finite number of states a UI element can exist in. If we're to design a Heading component, for example, it can be anything between h1 and h6 and can be bold, italicised, or underlined. It should be straightforward to codify this.

The Grid System

The first step before embarking on any design project is to understand how the grids are structured. For many apps, it's just random. This leads to a scattered spacing system and makes it very hard for developers to gauge which spacing system to use. So pick a system! I fell in love with the 4px - 8px grid system when I first read about it. Sticking to that has helped simplify a lot of styling issues.

Let's start by setting up a basic grid system in the code. We'll begin with an app component that sets out the layout.

Next, we define a number of styles and wrapper components.

Finally, we'll define our CSS styles in SCSS.

There is a lot to unpack here. Let's start from the bottom. variables.scss is where we define our globals like color and set up the grid. Since we're using the 4px-8px grid, our base will be 4px. The parent component is Page, and this controls the flow of the page. Then the lowest-level element is a Box, which determines how content is rendered in a page. It's just a div that knows how to render itself contextually.

Now, we need a Container component that glues together multiple divs. We've chosen flex-box, hence the creatively named Flex component.

Defining a Type System

The type system is a critical component of any application. Usually, we define a base through global styles and override as and when needed. This often leads to inconsistencies in design. Let's see how this can be easily solved by adding to the design library.

First, we'll define some style constants and a wrapper class.

Next, we'll define the CSS styles that will be used for text elements.

This is a simple Text component representing the various UI states text can be in. We can extend this further to handle micro-interactions like rendering tooltips when the text is clipped, or rendering a different nugget for special cases like email, time, etc.

Atoms Form Molecules

So far, we've built only the most basic elements that can exist in a web application, and they're of no use on their own. Let's expand on this example by building a simple modal window.

First, we define the component class for the modal window.

Next, we can define the CSS styles for the modal.

For the uninitiated, createPortal is very similar to the render method, except that it renders children into a node that exists outside the DOM hierarchy of the parent component. It was introduced in React 16.

Using the Modal Component

Now that the component is defined, let's see how we can use it in a business context.

We can use the modal anywhere and maintain the state in the caller. Simple, right? But there is a bug here. The close button does not work. That's because we've built all the components as a closed system. It just consumes the props it needs and disregards the rest. In this context, the text component ignores the onClick event handler. Fortunately, this is an easy fix.

ES6 has a handy way to extract the remaining parameters as an array. Just apply that and spread them over to the component.

Making Components Discoverable

As your team scales, it's hard to get everyone in sync about the components that are available. Storybooks are a great way to make your components discoverable. Let's set up a basic storybook component.

To get started, run:

This sets up the required configuration for the storybook. From here, it's a cinch to do the rest of the setup. Let's add a simple story to represent different states of Type

The API surface is simple. storiesOf defines a new story, typically your component. You can then create a new chapter with add, to showcase the different states of this component.

Of course, this is pretty basic, but storybooks have several add-ons that will help you add functionality to your docs. And did I mention that they have emoji support? 😲

Integrating With an Off-the-Shelf Design Library

Designing a design system from scratch is a lot of work and may not make sense for a smaller app. But if your product is rich and you need a lot of flexibility and control over what you're building, setting up your own UI library will help you in the longer run.

I've yet to see a good UI component library for React. My experience with react-bootstrap and material-ui (the library for React, that is, not the framework itself) wasn't great. Instead of reusing an entire UI library, picking individual components might make sense. For instance, implementing multi-select is a complex UI problem, and there are tons of scenarios to consider. For this case, it might be simpler to use a library like React Select or Select2.

A word of caution, though. Any external dependencies, especially UI plugins, are a risk. They are bound to change their APIs often or, on the other extreme, keep using old, deprecated features of React. This may affect your tech delivery, and any change might be costly. I'd advise using a wrapper over these libraries, so it will be easy to replace the library without touching multiple parts of the app.

Conclusion

In this post, I've shown you some ways to split your app into atomic visual elements, using them like Lego blocks to achieve the desired effect. This facilitates code reuse and maintainability, as well as making it easy to maintain a consistent UI throughout your app.