Moving to a Styled System

Jens Lind
Strise
Published in
5 min readFeb 3, 2020

As part of our rebranding from Mito to Strise, we completely revamped the design of our product. The perfect opportunity to rethink our approach to component based design. This post will be about how leveraging component props in React can help you build reusable and extensible UI components for fit in a design system.

Styled System you say?

Styling is hard, seriously, styling a large scale website in a structured and maintainable way really has its learning curve. Me personally have moved from just utter chaos to a more structured approach using the rules of BEM. Then moving on to automatically scoped CSS classes with CSS Modules and Sheetify. In the last year I have also been really into Styled-Components and “functional css” in Tachyons.

Then I discovered Styled System. It takes advantage of what Styled-components (or other CSS-in-JS libs) gives you, which are, scoped classes with the ability to use component props to determine styles. Styled System takes this one step further, and basically adds CSS props to your components. A bit simplified but this is how it looks:

// This is called a "style function"
// Returns a style object, parsed by styled-components
const display = (props) => {
if (props.display) {
return { display: props.display }
}
}
const Component = styled.div`
${display}
`
<Component display='block' />

Critics may say it’s confusing that you have to learn another way of applying styles, since for example, the CSS “margin” property becomes just m, and “mx, my, mt, mb, mr and ml” are introduced (see: https://styled-system.com/table). But this also solves an actual problem with props inconsistency. Since all components will have the same styling props attached to them, we will have no, or at least very little props inconsistency for applying styles to components.

We also got first-class access to the theme object. We could actually pass a number to m and it would multiple itself with the spacing value from the theme. Also this, yields great consistency across the project.

{
spacing: 4,
...
}
<Box m={2} /> // 8px margin

But for me, the biggest advantage to this approach is that every component becomes so damn easy to extend and override. Perfect for building a reusable component library in a design system.

Extend and override styles

The most common approach is that you have a base component usually called <Box>. The box is a building block and compiles to just a <div>. But it has what we call “styling props” attached to it. So you can do this:

<Box m='5px' />// Output:
.random-scoped-class {
margin: 5px;
}
<div class="random-scoped-class"></div>

Let’s extend the Box and build a useful component we can reuse, an image component for example.

const Image = (props) => (
<Box as='img' p='10px' mb='10px' {...props} />
)
<Image src='image.png' />// Output:
.random-scoped-class {
padding: 10px;
margin-bottom: 10px;
}
<img src="image.png" class="random-scoped-class" />

Now we can use the <Image> component everywhere we need to display an image, and it will always have a padding and a bottom margin of 10px.

And in specific places, we could easily override the padding for instance, but retain the rest of the default image styling:

<Image src='image.png' p='5px' />

If we wanted a padding of 5px in many places, let’s say all avatar images should have that padding. We could just wrap and extend the <Image> component just as the Box.

const AvatarImage = (props) => <Image p='5px' {...props} />

The `as` prop

This is a special one implemented in styled-components. It lets us change the element a component renders to. In the example above we set the as prop to be an <img>. The as prop takes any HTML element as a string or another React component. So you could also do the following with our <AvatarImage> component:

const AvatarImage = (props) => <Box as={Image} p='5px' {...props} />

Responsiveness with ease

All style props can take an object with your theme defined breakpoints. This will automatically generate CSS media-queries for you.

<Component width={{ xs: '100%', md: '50%' }} />

A styled system with Material UI

Material UI has its own styled system implementation, https://material-ui.com/system/basics/.

We already had Material UI deeply integrated into the product and decided to stick with it. The Box component in Material UI’s core package has the styling props attached to it already. So what we first did, was to use that. But Material UI is using the JSS engine internally, not styled-components and we had some serious performance issues when rendering a huge amount of components. This forced us to make the move towards styled-components. When switched we saw a huge boost in performance.

Material UI Core components

The core components does not extend the system as of now. Therefore you cant use styling props on Core components. So what we do is wrapping them in our <Box> component.

import { Button } from '@material-ui/core'<Box as={Button} />

This works quite well for us, but this approach also has some downsides, we now end up using two engines to compute styles 🤠. As mentioned, the Core package utilizes the JSS engine, however there is a long standing issue of moving to styled-components. But I have also realized that in most cases we don’t really need the Core components. I think we are far better to “re-create” many components, like Paper, Typography, Link and more to get 100% control of which styles being applied. But when we decide to use a Core component, and need to override some styles we can do that in the theme file:

overrides: {
MuiButton: {
root: {
padding: undefined // Removes the padding from the Button
}
}
}

If you are using JSS you would also experience some colliding props, which may result in some unexpected behavior. For example, a lot of Core components has the component prop which in JSS is the equivalent to Styled-components as prop. We solved it by first wrap and apply component to the Core component, and then Box it. This is how our HOC looks like:

export const Boxed = (Component) => (props) => {
const Wrapped = (wProps) => {
if (props.component) {
return <Component component={props.component} {...wProps} />
}
return <Component {...wProps} />
}
return <Box component={WrappedComponent} {...props} />
}

Thanks for reading!

To wrap-up, here are some pros and cons with using this approach.

Pros:
- Consistency of props across components
- First-class access to the theme (breakpoints, spacing, colors…)
- Easy to extend and override styles

Cons:
- You need to learn a new way of how to apply styles
- Most likely a loss in performance

Packages to checkout:
- https://styled-system.com
- https://material-ui.com/system/basics
- https://styled-components.com

Great posts on this subject:
- https://varun.ca/styled-system
- https://medium.com/material-ui/introducing-material-ui-design-system-93e921beb8df
- https://medium.com/styled-components/build-better-component-libraries-with-styled-system-4951653d54ee

Come work with us! We have great fun while building something we all believe in and are always on the lookout for new people to help us grow. Send us an email at jobs@strise.ai or checkout strise.ai/careers. We would love to hear from you!

--

--