The 5 Layers of a Design System
Picture a fully-fledged design system. You can bootstrap a new project, pull a bunch of components from the system, and have a page in minutes instead of days.
When you’re first starting to build your own, it seems too good to be true. You might be asking yourself, “how do I get from nothing to a system that supports multiple teams and hundreds of happy engineers?”
There’s a lot to consider, but in this post we’re going to talk about design system architecture, and specifically the concept of component hierarchies.
We can use the metaphor of a wedding cake to understand this a little better. When you see a wedding cake, your brain first thinks, “oh look, a cake.” As you look closer, you start to see that it consists multiple layers and elements.
Design systems are the same way. At first glance, a design system might seem like a single, monolithic library. In reality, a good design system is a series of layers that build on each other, ideally using progressive disclosure of complexity as a guiding principle.
The 5 layers
When a design system reaches maturity, it should have five layers, with the first three living in the design files and component library, and the last two living in the application code.
You may have heard of Brad Frost’s Atomic Design. He refers to the 5 levels in the component hierarchy in the following way:
- Atoms (in the design system)
- Molecules (in the design system)
- Organisms (in the design system)
- Templates (in the application code)
- Pages (in the application code)
Brad Frost’s language is really good, and very commonly used across teams. It’s not quite as specific as I’d like to really nail down the communication between designers and developers on my team, so I’ve decided to adopt Radius’ language and diagram:
- Design tokens (in the design system)
- Elements (in the design system)
- Patterns (in the design system)
- Features (in the application code)
- Layouts (in the application code)
Design Tokens
Design tokens are the most abstract layer of the system. They are an approach to storing style attributes like color, typography, and spacing in a pre-determined structure. While not particularly useful on their own, they are a critical part of a design system. They allow designers and developers to quickly ship redesigns, add theming to their applications, and build consistent layouts.
Ideally, your design tokens will follow a specific structure so that when you want to add a new theme — like dark mode — or you want other sites to be able to apply their own theme, it’s easy to do. You should agree with your designers on how to name and structure your tokens so that you can start to build a shared language between designers and developers on your team.
You might represent your design tokens in your application like this:
const theme = {
colors: {
primary1: "#07005B",
primary2: "#1D35AF",
secondary1: "#E34F30",
secondary2: "#3CA6E0",
secondary3: "#C17259",
secondary4: "#0E78B2",
secondary5: "#00875E",
},
};
Elements
Now that we’ve covered design tokens, let’s move up a level to elements. In my experience, this is what people usually think about when they imagine a design system. They’re certainly a foundational part!
The elements in the design system should know how to read your design tokens. As you’re building out your elements, you’ll want to think carefully about what parts of your elements can be overridden by a change to the design tokens, and which are hardcoded.
The most common example of an element is a Button component. It seems simple, but there’s a lot to consider!
Let’s use a RadioButton
for our example. If we’re writing React code, our RadioButton
might look something like this (although in real life it’ll have some props):
<RadioButton />
Some other examples include:
- Link
- Tab
- Checkbox
- Input
- Grid
- Box
Patterns
Patterns consist of two or more elements. They are more opinionated than elements. As design system maintainers, we make decisions about how elements are combined to create patterns. If consumers don’t like they way we have created a pattern, they are welcome to drop down a layer of abstraction and combine elements in a new way.
Let’s use the RadioList
to illustrate this point. A RadioList
is a group of multiple RadioButton
elements. I’d implement that component like this:
const radioList = [
{
label: 'Specialized Diverge',
value: 'diverge'
},
{
label: 'Specialized Roubaix',
value: 'roubaix'
},
{
label: 'Specialized Allex',
value: 'allez'
}
]<RadioList data={radioList}/>
Under the hood, the RadioList
would render three RadioButton
s with the correct labels and values.
Some other examples include:
- TabsList
- Form
- NavBar
Features
Features consist of two or more patterns. They are more opinionated and specific than patterns. As usual, consumers can drop down into the level of abstraction that’s right for their needs. Normally, features will live in application code, not in the design system. We can use a SurveyQuestion
as an example! A SurveyQuestion
might combine a RadioList
with a Text
component.
const SurveyQuestion = () => (
<>
<Text>What is the answer to this question</Text>
<RadioList data={radioList} />
<Button>Submit</Button>
</>
);
Some other examples include:
- ShippingAddressForm
- CrossSellModule
Layouts
Layouts consist of two or more features. These are the most opinionated layer of the system, and are usually so custom that it wouldn’t make sense for them to live in the design system. A good example of a layout is an entire survey.
const Survey = () => { return (
<>
{questions.map(question => {
return <SurveyQuestion question={question} />
})
</>
)
}
Some other examples include:
- Survey
- CheckoutPage
- HomePage
Let’s reiterate a bit. By the time we’ve reached the Layout level, we have an entire, customized <Survey />
that’s living on a site somewhere. But that <Survey />
is composed from many different levels (some of which live in application code, and others of which live in the design system).
The <Survey />
is made up of multiple <SurveyQuestion />
features. Each <SurveyQuestion />
is made up of multiple <RadioList />
patterns. <RadioList />
patterns are made from multiple <RadioButton />
elements. And RadioButtons
know how to read the design tokens.
This article was originally published on maecapozzi.com. Head over there if you like this post and want to read others like it.