DEV Community

Cover image for [Engineering] How to build a scalable design system
Mohammed Taher
Mohammed Taher

Posted on • Updated on

[Engineering] How to build a scalable design system

Atomic design was a blog article written in 2013 by Bradfrost discussing how to make proper design in a reusable and modular way, and even there is a book published for it, and the content itself is much refined from the very early blog article that was written.

Brad has not really invented something new, but what he did was frameworking what I see as common sense for mature UI designers, which is divide and conquer.

Actually divide and conquer is not only applicable for solving design issues, but works with any big issue where you need to break it down to smaller issues/chunks that can be easily solved

To my surprise, I have not really found really something similar on the Engineering side, maybe something like bit.dev is awesome, but it is not really about making an atomic design system, it is more like a modular framework any project, and their smallest unit is a component, but if we could say that component itself can be broken down to a more granular level

The Atomic Architecture Framework, which we will discuss in this article, is simply a set of rules which define how to manage the whole process of building a scalable design system in the most efficient way (from Engineering perspective), and also covers all common parts between Design, and Engineering

We can start by some definitions:

Atom:
The most broken down unit in the system, if it can be broken down more, then it is not atom. Single responsibility item
Props (if necessary) should be as primitive as possible, this means deep nested objects, or complicated arrays are not allowed as props (unless necessary)
Root tag is always custom and should match atom name
Semantic HTML should be always followed when possible

Molecule: A group of atoms to serve a repetitive purpose. A molecule is consisted of multiple atoms, molecules, or both together

Element: Exposed component that groups molecules, and atoms to feature Engineers. It can offer complicated models & props. It
Should reuse molecules/atoms as much as possible

Block: Block is a reusable piece of complicated Group, so for example user profile card where we render avatar, and some social profile icons with links, and maybe a button to message that user, follow, or block .. etc

Page: Page in frontend world corresponds to a single route, and a single page can render multiple fragments, or pieces of UI, and these pieces can be anything from defined above

and then we can define some rules:

  • Layering: The idea behind the breakdown above is to separate internal concerns from external concerns. Think of it as OOP private and public class methods, where you can only expose what you need to, via public props, or methods, and the rest is kept internal, so we guarantee a consistent contract, so no breaking changes at all. However we may want to introduce breaking changes, and that will be covered in versioning rule. The internal layers are simply atoms, and molecules (private for core team working on design system), while the external layers Element, Block, and Page are usually exposed to be used by our feature Engineers (for example)

  • One to One Map: The rule above will lead us to another rule which is 1:1 map, and this means that sometimes we may want to use an atom, or molecule as element, so you may find yourself in a situation where you just expose it ...

Image description

Just because the same exact functionality is required in the present time, doesn't necessarily mean that we expose our internal components, and the reason for that is; in future you may want to extend (or remove) some props from some atom because some element requires so, and then you will end up with a pile of unnecessary (or optional) props which really pollutes your code

We need to keep our code clean and separate the concern of atom, or a molecule, from the concern of higher level layers, and this means that even you are in a situation when you need to clone an atom (e.g. checkbox atom), it is totally fine to map at as 1:1 with checkbox element

  • Props vs Classes: This part is little bit tricky as it is always hard to decide if you should apply visuals via props, or classes, so for example we need to apply background color to some button, should we then create a class with background color inherited from some token (css variable), or should we expose a color prop where Engineers can change it?

There is no right, or wrong answer to this really, but there is a decision that can be made based on use case, to explain this in a simple way, lets assume we have a checkbox (again) that has some brand color matching our design system (assuming dark blue), and for some reason we want our Engineers to change the checkbox color (border or background) to red when used inside an invalid form

In this case we need a way to inform our Engineers that they can customize background color based on some condition, and for that reason a prop would make perfect sense, on the other hand imagine we need to change some background color of an atom, but for some internal reason (e.g. blur, or focus), which is already predefined in our design system, then it would make perfect sense to apply this via a shared class

Development strategy

  • Convenience: Design system, like any project needs to be well planned before executed, and for this reason, it might be helpful for the team working on it, to brainstorm together what needs to be done before writing any code

A nice way to plan the development of any component, is to think about possbile props, and methods in the way that is most convenient for consumers, and this process may require many iterations, and feedback before reaching a stable and satisfying state

  • Consistency: Using code generators to generate components from pre-defined templates, is another great way to develop a design system, with great velocity, and consistency.

Using generators doesn't only help Engineers to focus on details, forget about infrastructure (like naming conventions, testing ..etc), but is also very useful in case we need to do some patching for our components in the future to improve certain parts (overtime you may find out better solutions to old problems)

Publish

  • Preview: Most common way to preview a design system in modern time is storybook. There are too many alternatives like styleguidist, or patternlab, but based on personal experience storybook is the most robust, customizable, and scalable way to communicate our design system with external world

One little issue we had was to preview our internal components (e.g. Atoms, or Molecules) separately from elements, and for that we had to create what we call playground

A playground can be another storybook instance, or even a custom preview component, that help design system Engineers to preview, and visually test their components

  • Versioning: Versioning is a very nice concept, specially when it comes to make sure new changes to our components don't break a stable version that is for example already used in production

Using something like bit.dev can be very handy for managing versions of our components, and we leave it up to our consumer to decide when they want to upgrade

Top comments (0)