4 New Theme Based React UI Toolkits and Why Itā€™s Going To Change How You Think

Posted on 2020-01-29

styled-components created a new standard for how we do CSS-in-JS. It introduced a sweet spot between writing CSS and React components, and it looked like this:

With this new API you could:

  • Use something that looks like CSS, using the new template literal feature from recent ECMAScript versions
  • Create atomic-feeling React components
  • Still preserve flexibility with access to props and use them through out the template literal freely

Made for a very low-friction developer experience, that was widely adopted, and then widely imitated (in a good way). We now have the same styled API in Emotion and others.

From here on, styled-components evolved to include themes and a ThemeProvider as well as a rich ecosystem.

styled-components is considered to be one of the most popular libraries for doing CSS-in-JS, and with version 5.0.0 just out in 2020, it catches up on performance with no API breaking changes.

Emotion

Emotion is another great library for your CSS-in-JS. If we avoid comparing it to styled-components (there are many comparisons already), it seemed to have settled on a niche of infrastructure for component libraries. Though Iā€™m not sure as to why it did, Iā€™m guessing that for a long while, it was considered to have first-class performance, while styled-components wasnā€™t (this is not the case anymore with styled-components 5.0.0).

For this reason, many, if not all of the most important UI frameworks were built on Emotion, and not on styled-components.

Largely, Emotion and styled-components have similar but not identical API and developer experience. If you want, both have the styled API, and using styled is a safe way to build something that can work with both:

But, and this is where our story starts to become more interesting, Emotion had put emphasis (and bet) on their now widely-known css prop.

They state that their primary (better?) way to style components is through a special css prop that does a few things behind the scenes:

A few notes about the css prop (from Emotion docs):

  • Similar to the style prop, but also has support for auto vendor-prefixing, nested selectors, and media queries.
  • Allows developers to skip the styled API abstraction and style components and elements directly.
  • The css prop also accepts a function that is called with your theme as an argument allowing developers easy access to common and customizable values.
  • Reduces boilerplate when composing components and styled with emotion.
  • Server side rendering with zero configuration.
  • Theming works out of the box.

Obviously, although the css prop appear to be inline with your component, its contents are compiled, extracted and put as a standard CSS block.

The magic, in this example is done by the jsx pragma at the top. With it, React creates new components with the Emotion jsx function, which is a custom way to create React components, with the relevant css prop logic applied.

Performance Is No Longer an Issue

Performance was a key differentiator between CSS-in-JS libraries for a long while. Well it isnā€™t an issue anymore:

You might want to look at the styled-components 5.0.0 for a nice comparison.

With that out of the equation, we can have an honest look at the abstractions around UI frameworks or toolkits that were created using both Emotion and styled-components with a material view of what developer experience they supply us with.

Flexbox and Evergreen

Weā€™re lucky to be living in a Flexbox and Evergreen world today. This means, that for the most part we can use Flexbox and get away with handling browser layout quirks. Which also means we have a huge burden of hacking around browser support and bridging incompatible rendering on our own, lifted from our backs.

In turn this creates less code, less hacks, and more opportunity for CSS-in-JS frameworks to be a legitimate replacement for CSSā€Šā€”ā€Šwe no longer need that demanding skillset of ā€œknowing CSSā€, in the sense that knowing CSS was really about knowing the differences between browsers and how to bridge those creatively (read: find hacks) and in a reliable manner.

This also puts a lot of the classic grid systems largely out of a job. Building grid system with Flexbox is a trivial matter today.

The Beginning of an Idea

Before we move on, Iā€™d like to plant an idea in your mind. We said that the styled interface also allows using prop like any other React component. So basically this:

And now, if we wanted to use a theme variable, having that a theme is supplied as a prop named theme how about this?

The important bit of information is that we call our background prop bg, so how about we create this function?

This function builds theme accessor functions, and now, we can use it to have a bit more of an elegant approach in our styled clause:

And this is the idea I wanted you to have in mind. From here, you can create a wide range of tools: theme getters, variants, responsive styles and more, all based on the same principle.

In fact, many of the next-generation libraries weā€™re going to look at next use this idea as their basic building block.

Theme Oriented Styling

What if we take our idea further? Say we have this block:

Thereā€™s no such color bg, but if we already go through the process of "compiling" template literals from what looks like CSS to actual CSS, could we tweak our infrastructure to replace bg from a name to an actual value in our theme?

For this to happen we need to:

  1. Have a theme ready in our context and available for every component (we need to know where to get the theme from)
  2. Where does the actual value for bg sit? is this the background for button? is this just one of a set of agreed-upon colors?
  3. Have an idea of what actually is a theme

So basically if we answer for (3) we get free answers for our first two questions.

First attempt:

const theme = {
    bg: '#fff'
}

Second attempt, a bit more verbose:

const theme = {
    background: {
        bg: '#fff'
    }
}

Now I need to ā€œteachā€ my infrastructure code that all CSS props that deal with background need to key into the .background prop in our theme, always. Nice idea, right?

But, if you notice weā€™re dealing with buttons, we could also do this:

const theme = {
    buttons: {
        one: {
            background: '#fff'
        }
    }
}

And if we could, with a bit of imagination, use something like this instead of our styled interface:

And we hope someone is listening in to this variant prop, and pulls the correct style from the buttons section, and injects it into our styling for our particular button.

Well, surprise! all these ideas, and more, are part of what I call next-gen CSS-in-JS frameworks.

But all these ideas need structure, which if we think about where everything starts fromā€Šā€”ā€Šthis structure exist in our modeling of a theme.

This is where the system-ui theme specification comes into play. It is somewhat widely adopted, and as of this writing thereā€™s not much deviation from this ā€œstandardā€, which is good news.

Hereā€™s an example:

// example fontSizes scale as an array
fontSizes: [
  12, 14, 16, 20, 24, 32
]
// example colors object
colors: {
  blue: '#07c',
  green: '#0fa',
}
// example nested colors object
colors: {
  blue: '#07c',
  blues: [
    '#004170',
    '#006fbe',
    '#2d8fd5',
    '#5aa7de',
  ]
}

The theme specification takes care of:

  • Order and convention. With this we know where colors go, where spaces go and so on
  • Context. With a well-known spec, we can build tools (and people did) to apply proper understanding of what React component weā€™re trying to theme, and to pull the right style from a theme
  • CSS-prop to theme-prop mapping. The spec determines that, for example, the sizes theme prop apply to width, height, min-width, max-width, min-height, max-height CSS props

And this is how we use it:

More on the sx prop later, but the gist of it is that width is now super-powered with our theme infrastructure, because we know a CSS width is mapped to our theme sizes prop, we know it indexes into the 0 and 2 positions which may resolve to, say, ā€˜768pxā€™ and ā€˜1070pxā€™.

Why the array? Because we are responsive by default. In this example, [0,2] relate to mobile/desktop respectively (you can apply more breakpoints).

The Road to Design Systems

If we have a theme specification, that locks in our style guide, design tokens and lets us centrally apply a policy around our styles, then we have much of the work done for a pretty good design system.

The kind of design system thatā€™s managed centrally, in one place that is shared between engineering and design needs to have very transparent and comprehensible conceptsā€Šā€”ā€Šit needs to be almost as simple as a JSON file, and this is to a large degree what the theme specification represents.

You can see part of what you can do here.

Converging Concepts

When we look at next-gen UI frameworks like Theme UI, Rebass, Chakra, and others, we see a few concepts that are very similar, and luckily enough are converging so we donā€™t have to learn many different things.

Style props

Style props give us HTML 3.0 back again (kidding!):

<div mr="30px" width="200px"/>

Here mr is a shorthand for margin-right and width is CSS width, and not your regular HTML element (HTML 3.0ish) width.

The great advantage that style props bring is hackability and familiarity. You can use a component and not factor all of the edge cases into your component. This helps you create a component library thatā€™s more resilient to change, and for example, you donā€™t have to make components incorporate logic to care of their spacing (margin, padding) in order to lay themselves out in a page.

Variants

It seems that most frameworks now add the concept of a variant, which looks like this:

<div variant="button"/>

If I could pick a concept that already exists in CSS that you are familiar with, it would be to say that a variant is kind of similar to a CSS class. Only itā€™s not cascading, and you canā€™t apply many of these, and so on.

A variant lets you swap a componentā€™s style entirely, and to key into a theme style. So you can switch themes completely, each having their own set of variants, or you can switch variants:

...
variants: {
  button: {
    ..
  }
}

Variants can be generic, as in above, or can be component specific such as:

...
buttons: {
  primary: {
    ..
  }
}

And use it like so:

<button variant="primary"/>

And the piece of infrastructure thatā€™s supposed to figure all this out, context, variant mix-in and theming is your UI framework.

ThemeProvider

Having that weā€™ve established that a theme spec is one main aspect of what weā€™re doing here, almost every next-gen UI framework has a ThemeProvider or uses the underlying infrastructure's ThemeProvider such as Emotion's or styled-component's.

Basically itā€™s:

import theme from './theme'
const Index = <ThemeProvider theme={theme}>..</ThemeProvider>

Box

At a first glance Box looks like a simplistic component. However, what it holds is the entire infrastructure for figuring out themes, style props, variants and more. The idea is that there is a single smart component which you build on, and out of which all of your other components will derive.

So for example to create a button you would do:

<Box as="button" ...>

And of course, when we say Box we're missing just one more word to make a winning combo: Flex. And so, may of the newer UI frameworks adopted both Box to signify the main building block that has all of the magic in it, and Flex, your main layout component to design your UI with.

Concerns

Box contains multiple concerns, each takes care of a different aspect of a proper design system. Here's a breakdown (from theme-ui):

export const Box = styled('div', {
  shouldForwardProp,
})(
  {
    boxSizing: 'border-box',
    margin: 0,
    minWidth: 0,
  },
  base,
  variant,
  space,
  color,
  sx,
  props => props.css
)

This means that theme-ui's Box components supports variants, spacing and space-aware props that connects these with our theme, colors -- which is very similar, and your magical sx and css props that we discussed previously.

And of course it takes care of your go-to fixes such as stating that this is a border-box layout model.

A good starting point to understand each UI framework is to start looking at the source for the Box component. Here is Chakra UI Box component, which in this case includes concerns such as shadow and typography.

Composition vs. Inheritance

Weā€™ve been saying ā€œconcernsā€ a lot. If we look at React and the component approach from a birdā€™s eye view, we discover that weā€™re having two patterns hiding in plain sight: composition and inheritance.

Inheritance is augmenting and creating new components by using existing ones, so for example:

const Button = styled.div`
...
`

Here we take a div and "inherit" from it to create a Button.

When we look at the Box concept, we discover something different, this is actually composition:

export const Box = styled('div', {
  shouldForwardProp,
})(
  base,
  variant,
  space,
  color,
  sx,
  props => props.css
)

The traits, or concerns, base, variant and so on, are creating multiple inheritance trees or more specifically -- a concerns/mixin based model, which we know from Software Engineering is more flexible, resilient and adaptable.

So by opting into the new way to build design systems in React, youā€™re also opting into a better software engineering model.

Toolkit vs. Framwork

As my own opinionated view, I look at the following frameworks as toolkits, not frameworks. Itā€™s hard to accept a toolkit, because weā€™ve been used to all-in-one frameworks back from Bootstrap, and up to Antd.

But when you realize what Iā€™ve discussed up until now, you understand that with composition and a good way to model a framework you can build you own, on the condition that you donā€™t really need a full-blown enterprise-ready UI frameworks with complex data tables, calendars, dropdowns and more.

Rebass

Rebass was one of the most popular frameworks that pushed this new approach into mass adoption. Approaching both mobile (React Native) and Web, itā€™s been a simple yet pragmatic framework for a long while.

As weā€™ll see with Brent Jackson, it evolved, struck different roots in the process, which mind youā€Šā€”ā€Šis the better way to do things in software, into different and better frameworks.

Styled System

One of the infrastructure that we got from all that action was styled-system which is the infrastructure under most of these new-gen UI frameworks.

Itā€™s actually easy to build your own framework using styled-system, as you get many of the concerns for free.

Theme UI

Theme UI is an evolution of work that Brent already did, you can read about the history here. For a long while it had no components of its own, and so it was seen as a foundational framework, and in that sense people still preferred to use Rebass (also from Brent).

Recently, Theme UI got a components kit which makes it eligible for a one-stop-shop framework. Iā€™ve personally moved to using Theme UI exclusively instead of combining it with something else, like Rebass.

It introduces the sx prop, which is a way to combine themes and the css prop from Emotion.

Chakra UI

Chakra UI is a polished, complete and friendly framework. It builds on top of every concept weā€™ve mentioned here, and still, tries to be compatible with Theme UI for a good degree.

Styling after Styling

So once youā€™ve picked your UI framework, how do you customize your styles? There are a few options.

One, is use the sx, css, or style props to create your own variant of a component. For this, you'll have to create a new component:

const StyledButton = (..)<Button sx={{background: 'dark'}}>..</Button>

Another way, is to use forward refs and build a new component from an infrastructural point of view. In other words, itā€™s still just a Box. Here's how Chakra does it:

const Badge = forwardRef(
  ({ variantColor = "gray", variant = "subtle", ...props }, ref) => {
    useVariantColorWarning("Badge", variantColor);
    const badgeStyleProps = useBadgeStyle({ color: variantColor, variant });
    return (
      <Box
        ref={ref}
        display="inline-block"
        ....

Lastly, in the general sense you can still use styled-components, either the Emotion styled version or if for some reason you want to mix styled-components and Emotion (in reality it works but officially there are caveats):

const Button = styled(Box)`
  ...
`

If in doubt, since this entire theory that weā€™ve built in this article really creates less code and cruft, the best thing to do is to dive into the Theme UI codebase and see how new components are built.

With this added infrastructure magic youā€™ll be surprised how elegantly you can create new components and experiences without creating a lot of noise.

Finding Themes

Since themes are the new hot thing here, you can now mix and match and find inspiration for styling in themes that already exist. Take a look here at Theme UIā€™s packages list, and see how themes are made.

Not only that, if youā€™re a fan of Typography you have a toTheme helper that can convert a Typography theme to be a first-class citizen Theme UI theme.

Conclusion

I believe the above theory, discussion, building blocks, and implementation and showcases are the next step in UI frameworks, much thanks to the very observant and skillful Brent Jackson. I also believe that in software engineering, the healthy way sometimes is to build something, and then start from scratch a couple times to get at something perfect, which is exactly what was done in the case of the frameworks weā€™ve seen.