The Lean Design System

February 08, 2019 12 minute read

In the scope of this article I will explain what a design system is, how it works and the benefits it provides even for small hobby projects. Then we will build the foundations of a lean design system and see how it can be put to use.

Any tool that has the word “system” in its name looks daunting at first, but we will see how taking the lean approach for the creation of a design system actually speeds up and eases development.

What is a Design System?

A Design System in its best form is a collection of styling rules, design principles and limitations that a team uses to develop software. In the context of UI engineering this can be a collection of the application’s colors and rules on when to use them.

If we go one step further it can be a collection of pre-made components - each of them exposing an API that allows it to be configured. It’s also useful to have an explanation of when and how to use a component in the interface.

In general, a design system is more than that. While it should serve as a tool for developers and designers to produce more consistent products it can be used as a guide for the message that the brand wants to pass to its users. It can contain guides about what language and imagery to use for the sake of marketing.

Here you can find a gallery of such tools used by different companies like Atlassian, GitLab, SAP, Heroku and others.

Not Only for Big Corps

To me, implementing a design system sounded like a fun thing to do on the side, but I couldn’t really imagine using such a tool in my small hobby projects. After all, working on something in our spare time is about trying out new things and going fast. We want to avoid all the bottlenecks of the working environment.

Talking about user experience and consistency sounds amazing but let’s not lie to ourselves - most personal projects we do never see the light of day. They sit in a dark forgotten corner in our GitHub account. Now that there are free private repos they may not even get the glory of being pinned on the front.

I decided to give implementing such a system a chance. It ate some time at the beginning but allowed me to be more productive in the long term. I was able to put out features faster because I had the common building blocks in place. It’s also a one time effort. Once you have it you can reuse it in other projects.

A normalized way to structure our app’s styles will take a big burden of our chest when we are implementing a new component or a page. Think about how many times we stop and think - “Okay, how big should this text be?”, or “This need a bit more room to breathe, how much padding should I add?”.

The Benefits of Design Systems

Making some decisions in advance and using a fixed set of sizes and colors can save us a lot of time. If we have a predefined palette of colors we needn’t go to the color picker and waste time trying to find a fitting shade for a component.

If this project is something that we plan on launching for people to use then having a frame in which to create the UI should be even a bigger priority. A design tool like that will help us create a consistent UX and help the user feel more confident.

Repeating ourselves is bad when it comes to writing code. But having repetitive patterns in the UI is incredibly useful for the users.

Without a design system it’s easy to end up with inconsistend UIs. Similar components look differently because they were implemented by different developers. If we put some constraints we put a frame in which everyone needs to fit. This makes it easier to create consistent interfaces and a better user experience.

Putting personal projects aside, I also find such a tool useful in smaller teams or companies. Smaller teams value development speed and welcome everything that helps them to achieve it.

The McDonalds Experience

In order to better illustrate the importance of systems we need to step away from the technical implementation and talk about fast food restaurants. Particularly McDonalds restaurants.

Whenever we walk in a McDonalds we know exactly what to expect. You can go into any restaurant from that chain in the world, order a cheeseburger and get the same experience - including the questions you are asked, the time you wait and the way the burger is wrapped.

The furniture is identical, the way the store is arranged too. You never walk into a McDonalds and hope that the cook is good. No, you know what you are going to get no matter if you are in the US, Europe or somewhere in Asia (there are specifics depending on the culture but that’s a different topic).

While I’m not the biggest fan of fast food restaurants, the point I’m making here is that they have a well-defined system that gives their customers a sense of consistency and certain expectations.

Reusability

I’d argue that consistency is the single most important thing in the UI. But design systems are powerful because they let us take this idea further. With a design system we can achieve a common look and feel across multiple products.

You may be wondering why we’d want to do that in the first place. Your products may be operating in different niches or serving different audiences.

But think about Google’s apps - both on web and mobile. They all use material design and once you’re used to one of them you will find your way around all of them. They have consistency across their products, not just in each one of them.

If your work requires you to maintain multiple products with a visual interface, then the investment in a design system is worth it even more. The speed of development increases since you have the building blocks in place. You just need to compose them together.

The Lean Design System

A proper design system can be a project of big scope. It requires continuous effort and

But since we started the whole discussion with the idea of side projects, we don’t want to create a roadblock. We need to spend more time working on the features and business logic challenges rather than maintaining our tools.

We can borrow a page from the startup world and use the “lean” methodology to approach this problem. But what does “lean” mean?

Essentially, we are going to put together the minimum amount of constraints that our design framework needs. The smallest amount that can get us productive and going without having to modify it in the future.

In my opinion there are three main areas that we need to cover - colors, typography and spacing.

Colors

Colors are the most impactful tool we have to communicate with our users. They can be used to attract attention, signal an error or specify how important a piece of text is.

However, putting together a set of colors that fit together is everything but easy. Understanding colors is a science in which I’m not well-versed, but my fellow designers always have my back at work.

For personal projects, I use a pre-made color palette. You don’t need all the colors from it, just a few that make sense for the project. I usually settle for two or three fundamental colors and a set of supporting ones for genral purpose elements.

Primary Colors

Usually one of the brighter contrasting colors is chosen as the primary one. That’s the color that we will use to signal actions and it’s usually associated with the brand or the product.

It’s useful to have a brighter and a darker version of the color for highlighted and disabled states. We can use a hsl value and change the saturation and the lightness. This looks better and more natural than picking a new shade for each state.

:root {
  --color-primary: hsl(221, 44%, 41%);
  --color-primary-light: hsl(221, 32%, 65%);
  --color-primary-dark: hsl(211, 50%, 29%);
}

Supporting Colors

An advice I got from Refactoring UI is that we need a lot of grey colors. If you take a look at Bootstrap’s SCSS variables you will find that they use around 9 different shades of grey. Overall that color has many applications and it’s useful to have different options available.

Three greys are a good starting point that won’t put us in analysis paralysis. A black that is different from #000 looks better in general. A white color with subtle opacity is nice to have when we want to combine it with a bright primary color.

:root {
  --color-light-grey: #c0c5ce;
  --color-grey: #65737e;
  --color-dark-grey: #343d46;
  --color-black: #1a1a1a;
  --color-white: rgba(255, 255, 255, 0.9);
}

Use Generic Names

Try to avoid naming colors after specific components. It’s better to have a set of generic color names, instead of names like dropdown-background. This makes it much easier to change them or add themes in the future.

Typography

Colors are used to guide the user’s attention. Fonts are about the actual presentation of the content.

Fonts

To pick fonts for my app I go to Font Pair and find something that fits my needs. The number of fonts vary depending on the project. Start with one sans-serif font, but if some text needs to stand out you can introduce a second one.

If you’re in doubt, make a quick Google search about the best font for your use case. Serif fonts, for example, are usually recommended when reading long texts.

:root {
  --font-sans-serif: 'Montserrat', sans-serif;
  --font-serif: 'Merriweather', serif;
}

Font Sizes

In my side projects I used to end up with all font sizes between 18 and 30 pixels. In the colors section we started with just 3 variants but here we will need more. Often you will find that you need a size somewhere in between the ones you have. The app looks more proportional if we pick the sizes from the start.

:root {
  --font-size-xxs: 0.75rem;
  --font-size-xs: 0.875rem;
  --font-size-sm: 1rem;
  --font-size-md: 1.125rem;
  --font-size-ml: 1.5rem;
  --font-size-lg: 2.25rem;
  --font-size-xl: 3rem;
  --font-size-xxl: 3.75rem;
}

It doesn’t really matter if we are using fixed pixels or a relative unit. I find it easier to reason about the sizes if they are all derived from a base one. It’s also easier to scale the font size up or down depending on the device.

html {
  /** Change the base font-size depending on the device */
  font-size: 16px;
}

Spacing

Spacing is in my opinion the biggest factor for the actual feel of the UI. Having items either crammed together or too widely spaced is a big factor to how busy the interface feels.

The other thing that is easily overlooked especially in small projects is the consistency in spacing across the different components. Components with similar function but different spacing, whether it is padding or margin make UIs look inconsistant.

Picking spacing sizes is quite similar to picking font sizes. At the begining we think that we only need a few but with the first component that we make we realise that having just a few is limiting.

:root {
  --space-xxs: 0.25rem;
  --space-xs: 0.5rem;
  --space-sm: 0.75rem;
  --space-base: 1rem;
  --space-md: 1.25rem;
  --space-lg: 2rem;
  --space-xl: 3.25rem;
}

Components as Separation of Concerns

I’m a big advocate for React because of its approach to separation of concerns. The logic and markup of a component is put together in a single file. This makes it easy to modify presentation and behaviour in isolation. Libraries like Styled Components allow us to group styles in the component as well, grouping everything together.

Many believe in the horizontal or technological separation of concerns. Switching to a vertical model allows us to group all code that changes together in one place. This means that all changes to the markup, styles or functionality of a Button component will only affect the Button.jsx file.

Implementing the Design System

The implementation will be React and Styled Components specific, but the ideas are applicable in all or outside the UI frameworks.

Styled Components uses the Context API to share global styles that can be used across all components. To take full advantage of that we need to define the variables as a JS object.

const theme = {
  colors: {
    primary: 'hsl(221, 44%, 41%)',
    primaryLight: 'hsl(221, 32%, 65%)',
    primaryDark: 'hsl(211, 50%, 29%)',
    grey: '#65737e',
    greyLight: '#c0c5ce',
    greyDark: '#343d46',
    black: '#1a1a1a',
    white: 'rgba(255, 255, 255, 0.9)'
  }
  fonts: {
    serif: '"Merriweather", serif',
    sansSerif: '"Montserrat", sans-serif',
    xxs: '0.75rem',
    xs: '0.875rem',
    sm: '1rem',
    md: '1.125rem',
    ml: '1.5rem',
    lg: '2.25rem',
    xl: '3rem',
    xxl: '3.75rem'
  },
  space: {
    xxs: '0.25rem',
    xs: '0.5rem',
    sm: '0.75rem',
    md: '1rem',
    ml: '1.25rem',
    lg: '2rem',
    xl: '3.25rem',
    xxl: '5rem',
  }
}

Theme Provider

The <Theme Provider /> takes our theme as a prop and wraps the main component of our app. That will make the values available for all styled components deeper in the tree.

return (
  <ThemeProvider theme={theme}>
    <App />
  </ThemeProvider>
)

Now we can implement a component using by using values from the theme. We will define a background, font size and padding for all sides.

const Button = styled.div`
  background: ${props => props.theme.colors.primary};
  font-size: ${props => `${props.theme.fonts.sm}`};
  padding: ${props =>
    `${props.theme.space.xs} ${props.theme.space.sm}`};
`

We don’t need to copy and paste values. We won’t have to search and replace if we want to change a color. Those are serious benefits, but so far they haven’t given us anything that CSS variables can’t. In fact, the syntax can be confusing for someone that hasn’t worked with Styled Components before. Let’s see how we can improve it.

Improving the Syntax

Right now the values are deeply nested and a larger component would be hard to read. We can abstract the access to them with a few curried functions.

const color = color => props => props.theme.colors[color]

const font = font => props => props.theme.fonts[font]

const space = space => props => props.theme.space[space]

Those functions will receive the value we want to access and then the props from the theme, so we can have a cleaner syntax in the component.

const Button = styled.div`
  background: ${color('primary')};
  font-size: ${font('md')}};
  padding: ${space('xs')} ${space('sm')};
`

This improves the syntax to access the value from the theme, but in order to use the CSS shorthands we need to make multiple calls. Surely, we can do better.

Let’s go back to our utility functions and see what we can do. They all access the same theme object so we can extract that into another function.

const getTheme = themeProp => ...identifiers => props =>
  identifiers
    .map(prop => props.theme[themeProp][prop])
    .join(' ')

const color = getTheme('colors')
const font = getTheme('fonts')
const space = getTheme('space')

We have extracted the code to access the theme values in another function called getTheme. It is curried so it’s going to return the value the third time it is called. First, we pass the top level theme slice that we are going to access - colors, fonts or space. Then we use the rest ... operator that will allow us to pass multiple values at once. Finally the function will be called by Styled Components with the props object. We then go through all the identifiers and map them to a value from the theme.

const Button = styled.div`
  background: ${color('primary')};
  font-size: ${font('md')}};
  padding: ${spacing('xs', 'sm')};
`

Adding themes

This is a good foundation for our lean design system. The goal wasn’t just to put some constraints in place. In the end we want a design system which is simple and easy to use. We managed to create a clean and intuitive syntax, which can be used even if you don’t know the implementation details of the tool.

The simplicity of themes is also something that I like. Since they are plain JavaScript objects, we can easily override the default theme if our app needs that functionality.

<ThemeProvider theme={{ ...theme, ...darkTheme }}>...</ThemeProvider>

Tao of Node

Learn how to build better Node.js applications. A collection of best practices about architecture, tooling, performance and testing.