Creating a Design System

Sagar Gupta
Myntra Engineering
Published in
8 min readOct 28, 2022

--

Consistency is often an area which demands constant attention, especially in large organisations. There can be functional consistency, where I would want a feature to keep its behaviour that it was designed for, irrespective of where it is used in a platform. In frontend space, there can be design consistency, where I would want a UI element to not start losing its look and feel when browsing through different sections of the app. Multiple reasons are there for this kind of experience to emerge. When a lot of independently operating teams are involved in building a platform, cohesiveness in design language starts to lose, components get built multiple times over, code could become un-manageable and entropy may catch up! Many a time, developers/designers come and go, design ideologies change and evolution happens differently in different parts of the platform, leaving behind an inconsistent UI experience.

So, in this blog post, we will try to solve this problem by creating a system that addresses these issues, scales well and can serve us for next few years.

Methodology

Before diving deep, let’s think over our approach. We need to adopt a Component-driven development methodology that anchors the build process around components. It is a process that builds UIs from the “bottom-up” by starting at the level of components and ending at the level of pages or screens. Component based development is a modular approach that decomposes frontend monoliths and introduces benefits similar to that of micro-services at the front-end. Component systems should only contain pure and presentations components, which only deal with how the UI appears. They respond exclusively to props, do not contain app specific business logic and are agnostic to how data loads. These properties are essential in allowing the components to be reusable.

So when creating a component, we try to answer these questions:

  • What is it called?
  • What is it made out of?
  • What variants are needed?
  • How does it scale?
  • What style variables are in use?

Next, we should have a central theme in place. Theme is basically a design language of the whole platform that is owned and managed by the UX designers. It would have information like colour palette, type scale that should be followed by each and every component design. For example, a colour scheme and a typography scale can look like this:

By referring to the theme, all the designs for our UI elements need to be created. Once we have the component designs, we can start putting it in code. But before that, we need to figure out the component API that we want.

Component API design can be opinionated. It should work well with your end goals and requirements. Here, we recommend adding constraint based style props to your react components.

Tech stack

So far in the tech stack, I have mentioned React. So, let’s focus on React Components. I want to be able to style a react component with style props. That brings us to CSS-in-JS land. CSS-in-JS solution makes sense for our component library use case. It is easier to manage and scale, can be easily integrated with any frontend project without any extra configuration. Styles for a component are locally scoped and lives and dies with the component itself. We can easily create a custom theme object configuration which all components can adhere to. Global theme configuration will provide consistency for all UI apps, a reference for design language and a great way to scale with any UX design updates. A key library that I want to focus on, which will make CSS-in-JS solve more useful by providing constraint based style props, is styled-system. styled-system gives us utility functions to achieve this and works with a CSS-in-JS library like styled-components or Emotion (our library of choice).

So, we have figured out major actors in our tech stack. Let’s list them down:

Creating building blocks

Now, let’s look at an example of this set up:

This example showcases what has been explained above. We are trying to build a way in which the Box component can be styled. It can only accept some specific type of props for styling. That is defined by utility functions like color, space, layout, position etc., each powering its own set of styling props. Here I have worked with emotion as CSS-in-JS library. We can plug any other library of similar API.

Now, let’s see what else we can build with our generic Box component:

So, we are pretty much digging deep in code now. First example is Flex.tsx, where we are using our previously created Box.tsx to build a generic Flexbox wrapper. Using this, we are now starting to build our first reusable Design system component, Button.tsx. So, let’s identify critical pieces of our Button component here:

  1. It is composed of Flex (which we previously created) and another new component, ButtonComp. Depending on our use cases, we can have any kind/number of smaller components here.
  2. ButtonComp is using HTML button (provided by styled api here). It also allows us to style this component for color, space, layout, flex, typography, position. Additionally, it uses some additional props for button sizes and variants, which we will cover later on in this post.
  3. Our components can now accept style props like alignItems and justifyContent for Flex and other similar props passed down to ButtonComp from the consumers of Button.tsx.

Adapting variations

Now, let’s talk about those buttonSizes and buttonVariants pieces that looked kind of odd in that api. Both of these are created from variant api of styled-system. Let’s look at this piece of code:

What we have accomplished here is extremely useful for our design system use-case. We needed a way to identify a set of styles from a singular prop. This identification or grouping of styles can come from UX design sheets for that particular component. Using this buttonSizes utility function in the styled api, we allow consumers of our component to control a set of styles related to spacing and fonts with a single prop, buttonSize. Similarly, buttonVariants is also created.

Let’s see visually how our button size variation is looking like:

“default” size
“small” size

Setting up Theme

So, now majority of the pieces are in place for our Button.tsx. But if you have been paying attention, few property values seem unusual, like odd numbers for margin and padding values, or strings like ‘default’ and ‘small’ for font size. So it is time to introduce one final and critical piece, Theme.

I have already introduced the concept of theme, and reasons for having it. We will now see how it can be implemented and used in our current set up:

We have created a very simple theme set up, where we have what colours we want, what space scale should be, what font sizes are allowed etc. Now, we should have a complete picture of how things would work. When we say mr=3, it means having a margin-right of 6px (value of index 3 in space array). So, we have created a scaling factor of 2 here. If we provide fontSize as small, it means pick “small” key value from fontSizes object in the theme, which is, 12px. Similarly, if we provide borderColor as “pink.2”, it will pick #FF9FB5. So, in this way, we can add more configurations to our theme object. One thing to note here is styled system utility props map to specific scales in the theme. For eg. borderColor will map to colors key, fontSize will map to fontSizes key and so on. For complete reference, check this out. But how does this theme object itself get integrated ?

Putting pieces together

Now, our component code is done! We can start looking at using this component. This is where our theme object gets integrated. There is a ThemeProvider component provided by Emotion library. It internally uses React Context to pass down the theme as context to the component tree.

This way, our theme is now accessible to the components and the button will be styled and rendered appropriately.

Now, we have dealt with how reusable styled components can be written and used with a completely configured theme, now I want to shift our focus to another major part of the set up, build and maintenance of our component library. Creating components is one part of the process, but to ship it to other teams, we would need a way to create a library of such components, add all build tools, test suites, documentations etc. So let’s cover one way of approaching this set up.

Build tools

To kick start a typescript package development, tsdx is great tool that provides many things out of the box. It is a zero-config CLI that helps you develop, test, and publish modern TypeScript packages with ease. When we create project using its CLI, TypeScript, Rollup, Jest, ESlint, Babel, Storybook and all other plumbing is already setup with best practices. These include things like the ability to have different development and production builds, multiple bundle formats, proper lodash-optimizations, treeshaking, and minification to name a few. We can still go ahead and configure many things on our own in set up provided.

tsdx using rollup under the hood to create builds like bundle.esm.js or bundle.cjs.js. These are singular builds which will contain entire source code of our components, theme etc. If we want to create individually compiled files, so as to import them individually, we would need to create another separate build process. esbuild is a good choice here because of two reasons: a) it is blazing fast, so build time is essentially unaffected, b) it majorly focusses on parsing typescript and discarding type annotations. Type-checking is something that it does not do. But, we don’t care for that as entire source code is already being type-checked by the rollup or tsdx build process.

Conclusion

Let’s summarise. We are now able to write react components with typescript, document them using storybook, test them using jest and storybook and compile using rollup and esbuild. This is one of the many ways to build a modular design system. With all the tech choices and existing design systems available today, we can take inspiration and make optimal choices that suit all our needs.

--

--