Building a React typography system

Jez Williams
Level Up Coding
Published in
7 min readDec 26, 2018

--

My goal is to create a solid, flexible, React typography system that scales.

Below is a collection of tools and conventions put together to form a design system which is easy to use and scalable.

This is a breakdown of the process, examples, and how to implement it.

The objective

I love design systems. It is probably the German side of me that gets a warm fuzzy feeling when working with a solid design system and things just work. Previously, solutions like Atomic Design Patterns and ITCSS worked well but relied on the cascading nature of global CSS. Now that the web has moved to more isolated modular components, I wanted to create a solution that still has the flexibility that we had with good old CSS classes.

These were my main requirements:

  • Extendable styles: Ability to override the default styles using props.
  • Flexible markup: Ability to change the components HTML element.

Furthermore, the final solution should be simple, scalable, and DRY.

Getting started

When facing challenges like this I often start with the ideal end goal and reverse engineer it. So in my mind, I would want something like this:

The above example would satisfy both the main requirements. It is a styled component which has the flexibility to be modified visually and semantically. Although the example above is fairly trivial, there a few changes I want to refactor to improve the naming convention and abstract the names from the context of where it is used.

Good practices

This brief section is about the conventions I used for examples in the final solution, feel free to skip this if you’re already familiar with design systems.

Firstly, typography names. I’ve used various conventions for naming type styles, from planet names to cities, but the best solution I’ve come across is the one used in GEL, the front end framework made by the BBC which references traditional type measuring techniques dating back to the 15th century.

http://www.bbc.co.uk/gel/guidelines/typography

This helps us easily communicate type choices in the development process, bridging the gap between designers and developers.

Secondly, regarding colours names I often use name that colour to create individual abstract names. This prevents ending up with lists of numbered variations of the same colour (grey-1, grey-2)… we’ve all been there.

So after applying those changes, the first example would now look like this:

Now for the next challenge, extendable styles…

Extending default component styles

To satisfy the first requirement of the criteria, I needed a way to make each of the typography styles (Canon, BodyCopy, etc.) into components with default styles but can also have them overridden using props.

Previously, I approached this by including all the default styles, props, and markup in each individual component (see below). However, this made modifications very limited and forced you to use the hard coded HTML element (in this case, H1).

Moreover, this approach also didn’t scale well and quickly lead to component and prop bloat once I added more styles and modifications.

After creating and trying various other possible solutions I came across the styled-system package Brent Jackson.

Using styled-system I was able to refactor the Canon component to achieve the following:

  • remove the manual props for modifications
  • move the default styles to the ThemeProvider

So, first I replace the manually defined props. Styled-System uses the concept of style functions to provide you with a set of utilities that map props to your component. So, after applying these from styled-system the previous example would now look like this:

I have been using styled-components but styled-system can also work with other CSS in JS libraries.

By specifying styled functions with styled-system, I was able to achieve the same functionality as before and also able to override the default styles like the font size and colour using props.

Step two, moving the default styles from the individual components and into the shared React Provider. Styled-system actually leverages the ThemeProvider used by styled-components, and by using the styled-system naming convention you’re able to reference values without any additional logic.

The code below is the theme variable that will get passed into the ThemeProvider. The textStyles object is where I moved all the default styles I had in the original component.

To make these styles responsive properties such as fontSize and lineHeight also accept an array in which the values correspond to the fontSizes array in the root object and the position in the array corresponds with the minimum width values in the breakpoints array.

The textStyles can now be referenced and applied to JSX elements to have their styles applied to it.

However, to make these text components easier to import I made a single manifest file which exports all the text components with the default styles and props passed in.

Text styles can now be imported and have styles overridden like this:

Lastly, for the final requirement, I also needed to be able to override the markup of each element too.

Flexible markup

For any design system it is important not to have the typography styles bound to an element. For example, you shouldn’t be forced to use a H1 whenever you need a header style. Semantic mark up should be just for browsers or search engines. And styling used for visual purposes.

To achieve this flexibility, I created a higher order component to handle all style and markup overrides called DynamicComponent.

Previously I achieved this by using the withComponent method from styled-components. This defaulted the DynamicComponent to a div and allowed it to be overridden using props (below).

However, as correctly pointed out by Ryosuke, this HOC is no longer necessary as styled-components V4 provides an as tag to modify the markup at runtime. So after this update its possible to just export a styled-component.

Each typography component will share this DynamicComponent. Therefore, we can move the styled-system props from each of the typography components over to the DynamicComponent keep the components DRY.

So the complete DynamicComponent with all the styled-system props will now look like this:

This now means that each of the typography components can now use the shared DynamicComponent. The default HTML element used when importing any text component is defined in the theme object with the as key to support the styled-components as method.

This is the final typography manifest file and is exporting 2 typography styles, Canon and Trafalgar.

./Typography/index.js

That’s it, sort of

So, finally, this is now possible: dynamic markup and styles.

I’ve included more examples on codesandbox and also using it on my site:

However, there are some small limitations I’ve come across:

  • The DynamicComponent could eventually bloat up. Because whenever any of the typography styles requires a new prop it would have to be exported from styled-system and added to the props list.
  • You’re also limited to only styles that styled-system supports.

So there is definitely improvements to be made but I’ve found it to be the most flexible and easiest to use so far. The full repo is here if you’d like to try it out or pick and choose idea for your own projects.

Thanks for reading ✌

I’d love to hear any feedback, thoughts or questions.

Photo by Amador Loureiro on Unsplash

--

--