How to Build a Design System
Functionally Driven Design
In early 2019 we had finished a series of feature rich releases and found that many of our patterns were beginning to require significant deviations from their base in order to accommodate increasingly complex functionality. We began the process of incorporating the analysis of the deviations into a new design system with the aim of building out a flexible toolkit to be used by both designers and developers to quickly assemble consistent interfaces.
Stage 1: Establishing a Firm Foundation
It was decided from day 0 that the design system would be built, maintained and documented in the same environment that it will be used in. The thought being that, having the design system effectively build itself, would force us to confront implementation complexities and help keep the focus on developing a visual language that was based on functional needs.
The next major step was to establish some guiding principals for this new visual language to revolve around. Our content is dense. When a page loads, more often than not, the user will have a lot of visual stimulus to parse through and take actions from. We wanted to reduce the cognitive load as much as possible for architectural layout and interaction signaling so the keywords simple and clear would be the core values of this design system.
Putting Together Some Basics
A maker needs some tools to create with, so we assembled the most basic of needs, Typography, first. Choosing a type system immediately puts us on the path of making supporting decisions about sub-systems that will ripple out in use as we continue to develop the design system. Namely determining the type-scale and initializing a color palette.
Stage 2: Decomposition
We could have focused on building a collection of common UI elements, but then we might as well buy a UI kit and call it a day. The trouble is that these frameworks seldom solve your business problems and can ship with highly opinionated element rules that developers will spend the next several years fighting against. So as an exercise in creating robust patterns, we opted to deconstruct views into functionally descriptive needs as way to abstract what was in front of our eyes and instead begin the thought process from an objective analysis.
In other words, name the things that the page does in functional terms not technical terms or business terms. So while an implementer might start using terminology like, "we need a select element that trims out the underlying option entities ...", we want to focus soley on the function, "we need to be able to search against a list of things to pick one". This is the pattern and we can riff off the baseline of a searchable select list and produce variants like a searchable multi-select list or a searchable multi-select lists with confidence information. The interface will provide increasingly richer expereiences as the complexity turns up, but there will be one common base that establishes the pattern.
Break out Dependencies
Next we select a component from the view and generate a mock to decompose into its base dependencies. Our goal is to identify the sub-components that yield the finished product and track whether or not there is an existing entity that suits the need.
Stage 3: Design, Build, Document
When we encounter a base entity that has not been built out, we formally design a final form with anticipated variants, code the solution and finalize the documentation with additional guidance on usage.