When I wrote about the idea of declarative design it really resonated with a lot of people.
I think that there’s a general feeling of frustration with the imperative approach to designing and developing—attempting to specify everything exactly up front. It just doesn’t scale. As Jason put it, the traditional web design process is fundamentally broken:
This is the worst of all worlds—a waterfall process creating dozens of artifacts, none of which accurately capture how the design will look and behave in the browser.
In theory, design systems could help overcome this problem; spend a lot of time up front getting a component to be correct and then it can be deployed quickly in all sorts of situations. But the word “correct” is doing a lot of work there.
If you’re approaching a design system with an imperative mindset then “correct” means “exact.” With this approach, precision is seen as valuable: precise spacing, precise numbers, precise pixels.
But if you’re approaching a design system with a declarative mindset, then “correct” means “resilient.” With this approach, flexibility is seen as valuable: flexible spacing, flexible ranges, flexible outputs.
These are two fundamentally different design approaches and yet the results of both would be described as a design system. The term “design system” is tricky enough to define as it is. This is one more layer of potential misunderstanding: one person says “design system” and means a collection of very precise, controlled, and exact components; another person says “design system” and means a predefined set of boundary conditions that can be used to generate components.
Personally, I think the word “system” is the important part of a design system. But all too often design systems are really collections rather than systems: a collection of pre-generated components rather than a system for generating components.
The systematic approach is at the heart of declarative design; setting up the rules and ratios in advance but leaving the detail of the final implementation to the browser at runtime.
Let me give an example of what I think is a declarative approach to a component. I’ll use the “hello world” of design system components—the humble button.
Two years ago I wrote about programming CSS to perform Sass colour functions. I described how CSS features like custom properties and calc()
can be used to recreate mixins like darken()
and lighten()
.
I showed some CSS for declaring the different colour elements of a button using hue, saturation and lightness encoded as custom properties. Here’s a CodePen with some examples of different buttons.
See the Pen
Button colours by Jeremy Keith (@adactio)
on CodePen.
If these buttons were in an imperative design system, then the output would be the important part. The design system would supply the code needed to make those buttons exactly. If you need a different button, it would have to be added to the design system as a variation.
But in a declarative design system, the output isn’t as important as the underlying ruleset. In this case, there are rules like:
For the hover state of a button, the lightness of its background colour should dip by 5%.
That ends up encoded in CSS like this:
button:hover {
background-color: hsl(
var(--button-colour-hue),
var(--button-colour-saturation),
calc(var(--button-colour-lightness) - 5%)
);
}
In this kind of design system you can look at some examples to see the results of this rule in action. But those outputs are illustrative. They’re not the final word. If you don’t see the exact button you want, that’s okay; you’ve got the information you need to generate what you need and still stay within the pre-defined rules about, say, the hover state of buttons.
This seems like a more scalable approach to me. It also seems more empowering.
One of the hardest parts of embedding a design system within an organisation is getting people to adopt it. In my experience, nobody likes adopting something that’s being delivered from on-high as a pre-made sets of components. It’s meant to be helpful: “here, use this pre-made components to save time not reinventing the wheel”, but it can come across as overly controlling: “we don’t trust you to exercise good judgement so stick to these pre-made components.”
The declarative approach is less controlling: “here are pre-defined rules and guidelines to help you make components.” But this lack of precision comes at a cost. The people using the design system need to have the mindset—and the ability—to create the components they need from the systematic rules they’ve been provided.
My gut feeling is that the imperative mindset is a good match for most of today’s graphic design tools like Figma or Sketch. Those tools deal with precise numbers rather than ranges and rules.
The declarative mindset, on the other hand, increasingly feels like a good match for CSS. The language has evolved to allow rules to be set up through custom properties, calc()
, clamp()
, minmax()
, and so on.
So, as always, there isn’t a right or wrong approach here. It all comes down to what’s most suitable for your organisation.
If your designers and developers have an imperative mindset and Figma files are considered the source of truth, than they would be better served by an imperative design system.
But if you’re lucky enough to have a team of design engineers that think in terms of HTML and CSS, then a declarative design system will be a force multiplier. A bicycle for the design engineering mind.