Note: What follows applies primarily to applications that benefit by having a coherent look & feel across the entire application, possibly reflective of the owning brand. For example, an online shopping application that needs to have a coherent look & feel that reflects the company's brand attributes. It may not apply to other kinds of applications that need to allow a high degree of customizability, for example, a customizable CMS application.
Many businesses, especially those that are brand conscious, are now understanding the need for a design system that is reflective of its brand attributes. However, unfortunately, the rigor which is applied in developing a design system is not carried on to ensure its implementation such that it can scale well. This is primarily because there is a translation step between design and implementation and the purpose of having a design system gets lost in this translation. There is a communication gap. However, until recently, there were technical limitations too in preventing deviations in the implementation. CSS, the styling language for the web, was designed for flexibility. CSS must be flexible and hats-off to the people who made CSS that way because I cannot imagine any other language which would be required to balance between so many conflicting requirements as CSS. However, when it comes to implementing design systems, flexibility can hurt scalability if the customization points are not well defined and controlled.
Fortunately, with the advent of UI libraries like React and the emergence of web components, the gap between design and implementation is narrowing. A component library built using any of these technologies is becoming a very effective way to ensure that the design system scales well. Furthermore web standards like shadow DOM and tools like styled-jsx, styled-components and CSS modules are extremely useful in building a component library that can scale. And tools like react-styleguidist, Storybook and Docz help generate a living design system documentation.
Although the tools for building component libraries have gotten quite mature, there is still a dependence on component developers to ensure that styles are encapsulated sufficiently. Style encapsulation is the heart and soul of making the design system scalable.
Style encapsulation could break when component designers allow the users of the component to customize styles in any of the following ways:
- Style injection: Components takes styles from outside and apply them to the elements inside them.
- Allow adding class names to the component markup.
- Using global styles
I have seen many times that developers feel fully justified in allowing such customizations. However, in most such cases it is also true that they may not have understood their design system fully or they are reluctant to go back to the design team and discuss alternatives or get clarity.
Whenever we are about to break style encapsulation it is important to see if that can be avoided. Some ideas:
- Discuss it!: We developers tend to see the design team as one individual. However, our assumption that all design team members are on the same page about the design system could fail more often than we can imagine. So, if there is a mock-up that shows a slightly different variation of a dropdown compared to an existing one, developers should go back and confirm if that's intentional. I am amazed at how many times a simple human to human communication solves a problem while we are grappling to find a technical solution for it.
- Think in terms of variants: If the variations in a single component are genuine then define the component to have multiple variants. The users of the components can only specify which variant of the component to render. The component itself manages the variant-specific styles internally. These variants would be very well defined in the style guide and hence not random customizations. Example, Primary button, and a Secondary button. In one of my projects, I saw a Button component that accepts styles form outside. We developers love to build things to be flexible. We don't like the idea of the design team coming back to us and saying we would need to build another variation of the component and that would require us to modify the component code. Hence we make the component flexible and leave it for others to customize it as per their needs. However, when working on a design system we should also respect the need for constraints. There is no successful design without constraints.
- Specialize the components: Sometimes the amount of variation between two components of a similar nature is quite high and doesn't violate the design system guidelines. In such cases may be having a completely different component is justified instead of making one component too flexible. For example, Button with Primary and Secondary variants as one component and Split button or Icon button as different components. Instead of generalizing a component to handle too many variants, sometimes, it pays to think in terms of multiple specialized components.
- Wrap it up!: Then there's the case of open-source UI widgets. They are designed to be flexible. And that is justified. However, if we are using such widgets within our UI component library, we could wrap the widget in our component that customizes the widget as per our design system guidelines and everyone else could use the wrapper component and not worry about customizing styles which can be redundant and hence deviate quite a bit.
Finally, from an organization's standpoint, it is important to highlight the collective ownership of a design system by the design team and the implementation team. Not having that synergy causes scalability issues.
If you've seen other ways in which we could break style encapsulation, do share. There are some other aspects of building scalable design systems that we will discuss in subsequent posts.
Thanks for reading!