To embrace change, you have to make it easy and cheap. System-driven design is one way to do just that.
Waterfall, the double diamond, and other traditional ways of approaching the development of digital products rooted in the building perspective take for granted that it’s easier to make changes to designs than code. They operate under the assumption that this graph describes the general arc of how the project will go:
This assumption isn’t baseless. It comes from decades of experience. This is what we usually see in our projects. Under this reality, anything other than Big Design Up Front is irresponsible. It doesn’t matter that we’re going to learn a lot more about how to accomplish this work as we do it, because by then, the cost of making changes will be so great that it would be impractical to do so. As deeply flawed as our assumptions and plans at the beginning of the project may be, there’s simply no other way to make digital products. It’s going to be a lot cheaper to catch a problem in a design than if we catch it in production. The cost of change keeps going up as the project goes on, so our only option is to be as deliberative and rigorous as we can. That’s something that building perspective processes like waterfall and the double diamond excel in.
As often as we’ve all experienced this, though, this curve does not reflect any immutable truth of digital product development — it’s just the outcome of bad (though sadly common) approaches to programming.
Embracing Change
Dave Thomas is one of those seventeen men who met in Snowbird, Utah in 2001 to write the Manifesto for Agile Software Development. 14 years later, he declared Agile’s demise. He argued that though the term had been damaged (possibly beyond repair) by its transformation into a noun (and worse still, a proper noun!) and commodified into the flagship product of the Agile Industrial Complex, the core concept of agility was as important as ever. And he laid out a simple algorithm for achieving agility:
- Understand where you are.
- Take a small step towards where you want to be.
- Evaluate what happened.
I have frequently cited this definition of agility in the past, but Dave also provides a principle that is critical to facilitating this, which is the part I’d like to focus on today:
When faced with two or more alternatives that deliver roughly the same value, take the path that makes future change easier.
According to Fred George, this is getting at the secret assumption that makes agility possible: programmers write lots of tiny, atomic functions and classes, which can fit together like Lego bricks to build more complex features. This creates a sort of exponential growth curve. At first, you have a few functions that are so tiny and simple that they hardly seem worth writing at all, but after a while you’re able to put together incredibly complex features by clicking together simpler functionality in a new pattern. The more you grow your base set of simple functions, the more powerful everything that’s built on those functions becomes.
This is the key to embracing change in the way that Extreme Programming demands. If the cost of change increases as the project goes on, then we can’t embrace it. Change is a risk that needs to be managed. We need to make sure we get it exactly right from the beginning, because making the change later on will be too expensive. Unfortunately, we’re mortal beings who dwell inside of time and space, so we don’t know what the future will bring, so any process that relies on us being able to predict the future is doomed to fail. To work within the limitations of our linear experience of time, we must embrace change, which we can only do if we can change that curve.
That’s precisely what Dave Thomas’s principle, the coding practices that Fred George talks about, and Extreme Programming practices allow us to do. Instead of the graph above, they give us this one:
This simple line brings with it some radical implications. Just as we argue that we need to do Big Design Up Front because it’s cheaper to change a mockup than it is to change code, this line makes Big Design Up Front an irresponsible waste of time and resources, because it will be much easier to make changes and figure out complex problems later on. Just as the previous curve demands that we be deliberate and rigorous before we start working on the product itself, this curve demands that we get started as soon as possible and learn as we go. With this curve, it’s getting started that’s the hardest part. Everything will get easier as we go. Where the first curve forces us into the building perspective, this is a curve for the dwelling perspective.
This is called evolutionary design, something often heralded as agility’s single most valuable practice. It doesn’t mean that we skip design, but it changes our relationship to it. We don’t design and then build; instead, we recognize that designing and building are one and the same, and embrace that. Joshua Kerievsky describes the process as beginning with a primitive whole (minimum-viable product might be the perfect term for this, were it not so widely misunderstood). As Joshua points out, the primitive whole is a step in creating the curve above, not necessarily something that you ship to users. “You ship when you are ready to ship,” he writes. That’s a question entirely orthogonal to a process of iterative improvement.
Evolutionary design is about software design, though, not design as UX professionals mean it. It’s more about software architecture and technology stacks than what a user actually experiences when they use the product. So is any of this relevant to UX design? Maybe our colleagues in software development can make good use of this, and that could shift some of our priorities, but this isn’t relevant to UX design, right?
Designing Systems
In the early 2010s, well before the term “design system” became a regular part of our world and we were all still struggling with the implications of Ethan Marcotte’s A List Apart article introducing the concept of responsive design, Stephen Hay gave a presentation at BDConf where he said:
We’re not designing pages, we’re designing systems of components.
Perhaps Stephen got this insight from someone else, but it was the first time I heard it, and I haven’t been able to track down any earlier statements (though that certainly doesn’t mean there weren’t any).
Responsive design had revealed a problem with our mockups that had always been there, but from which we had previously been able to hide: we strive for control as designers, so that we can create a precise experience, and while our mockups provide a comforting illusion that such control is possible, the web itself has always defied us. Before 2010, though, designers could sweep that under the rug. It was the developer’s fault if it didn’t look quite right, or the user’s for trying to view the webpage on the wrong monitor or the wrong browser. Our perfect 960px grids might float in a vast empty sea on a large monitor or scroll from left to right on a small one, but within our carefully-delineated domain, all was as it should be.
Responsive design made it clear that we could never produce enough mockups to capture all of the ways in which websites might be experienced. We tried to paper over the core concept with a few key breakpoints based on popular device sizes. It wasn’t really responsive, but we could call it that, and it would spare our mockups. We might need two or three or six mockups per page instead of the one we needed before, but we could still make this work!
Many designers kept working like that long after Hay’s talk — some even continue to work like that to this day — but this idea that we’re designing systems rather than pages began to take hold. It’s a shift that’s still working its way through web design. Today, design systems are ubiquitous and inescapable, though many designers still approach them through Figma or InVision, trying to hold them at arm’s length, always keeping a design tool of one kind or another between them and the system itself. Designers can work in their tools on representations of these systems in a context where they can maintain control, and front-end developers can take responsibility for translating those representations into the actual system itself, with all of its ambiguity and squishiness.
When we design pages, we’re subject to the first curve, the one in which change becomes more expensive the further we get into the process. When we work on pages and discover that we want to make a change to a design element that’s used across many, a change that’s worthwhile when we have to change five pages might be too expensive to consider if we have to change fifty.
In many of these situations, Figma can help a great deal — if we’ve been diligent in properly using components and styles. These features are Figma’s way of shifting our focus from pages to the system. If we want to change the primary text color and the accent color, it doesn’t matter that they’re used across dozens of mockups: we can just change that style and see that change applied across all of them. By shifting our focus from pages to systems we reduce the cost of change.
Design systems can be a contentious topic among designers. Some claim that they kill creativity; that they create lifeless, joyless pages made up of cookie-cutter components; that they rip the heart right out of design.
In addition to being a designer, though, I’m also a developer, which is where I’ve heard rants like these before.
Test-Driven Development
One of those core practices from Extreme Programming that allows software engineers to make change cheap is test-driven development (TDD). Like agility, this is a practice that follows a simple algorithm that you repeat over and over again:
- Write automated tests until you have a failing test.
- Write just enough code to make the test pass.
- Refactor the code until it’s good.
Outside of TDD, engineers usually write automated tests after they write code, and while good developers hit the highlights of how a system should work, it’s almost never as robust as when you need a failing test to give you permission to write a line of code. A comprehensive test suite that can give developers confidence that their software does what they think it does and doesn’t introduce any regressions or other nasty side-effects is just a fringe benefit, as far as TDD is concerned. TDD limits over-engineering, because it documents the reasons for every code change with a test. That helps keep code simple, which makes it easier to read, easier to understand, and above all, easier to change later on. A robust testing suite means that we can make changes quickly, easily, and with confidence that we won’t break something somewhere else in this enormous, complex web of a system that we’ve created (which directly addresses the main reason why the cost of change increases in more traditional approaches).
Just as with designers and design systems, there are developers who despise TDD. They say it’s a bunch of useless overhead work that exists only so that we can claim we have this trendy thing, that it puts our focus on passing the test rather than writing good code, that it removes the space for creative problem-solving and rips the heart out of good programming.
Does that sound familiar?
Of course, TDD doesn’t restrain creative problem-solving. That crucial third step of refactoring is all about getting creative and writing a great solution. All it asks is that you establish what success means (iteratively, as you go and learn what success means by doing the work) and establish guidelines so that you can know if your work is actually a solution or just making things worse. When you realize that there’s more you want to do, extending the system is as easy as writing a new test — which is where the cycle always begins anyway.
The same is true of design systems. As Fabricio Teixeira discussed at an Adobe Max talk in October 2021, there is a subtle dance to breaking and building a good design system. There’s no one right answer to where that balance is; how you move back and forth between them will define what makes a given design system unique.
The parallels made me wonder: could a design system do for UX design what TDD does for development?
A Case Study
At the end of 2017 and the beginning of 2018, I had the opportunity to find out. I was part of a two-person UX team tasked with delivering a new website with a small back-end development team. The timeline was aggressive. All estimates are lies, but I didn’t think it would be possible to deliver the website that our bosses wanted in the time they had alloted — at least, not if we approached it in the traditional way, making pages.
My title was “UX Engineer,” reflecting my combined design and development capabilities. My colleague was a UX Designer with a more traditional background. She was more comfortable working with pages, and that was what our stakeholders were more comfortable with as well, so that’s what she did. Rather than implement those pages directly, though, I made the design system that would have created those pages.
But if you don’t have enough time to finish the website, some of our stakeholders pointedly demanded, how could you possibly have time to create the website and a design system?
Because making the website on its own would be a linear process, but making the design system was exponential. I could make new components out of older ones, so each one that I made improved my ability to make new ones. When it’s ready, we can use the design system to make the website in hours.
There are two perils here, though:
- Being slightly off with an exponential growth curve like this can be the difference between making your deadline and missing it by a month.
- Throughout most of the project your progress will lag behind the level that your stakeholders will expect to see from linear progress, right up to the very end, when it will shoot past it.
That last point I referred to as “the anxiety gap,” and it was the biggest hurdle we faced. No matter how many times you explain it, human beings never really grok exponential curves. We intuitively expect to see something more like linear progress, even when we know intellectually why we won’t. When you don’t see that, and your whole company is on the line, you can get a little uncomfortable. Uncomfortable people make bad decisions, and when those people hold all the power, those bad decisions can wreck companies and end careers. So managing our stakeholders’ emotions throughout this process was a bigger obstacle than any technical or design challenge that we faced.
My colleague’s mockups were immensely helpful in this regard, but the fact that the pages didn’t yet look like those mockups was a constant source of dread. We had to constantly reiterate that things were actually going according to plan, as much as it might not feel like it — which, as rational as it was, became less convincing the closer we got to the launch date.
My colleague was (and still is) a highly skilled and talented professional. The mockups she produced were really great. But she was under pressure to churn out mockups as quickly as possible, and under pressure like that, and focusing on pages, some things were bound to come up. Implementing a design system proved to be an invaluable safety net, just like a good suite of unit tests. Since I had to organize our components and provide guidance on when to use one over another, when a mockup introduced a new type of list, it gave us a moment to stop and consider why. Often we had a good reason, and I simply wrote that reasoning down. A few times this exercise pointed out that it would make more sense to use a component that already exists. Even when we kept it, it challenged us to think about our design more systematically. When we decided to reuse an existing component instead, it served as a guard rail that kept the design coherent.
Our system was put to the test less than two weeks before launch, when all the executives spent the entire afternoon and late into the night in a meeting where they decided to significantly change direction on how they were positioning the product. Had we been following the traditional path, this would have been a disaster we would have no hope of recovering from. For our colleagues in marketing, it was very nearly that. Because of our design system, though, we were able to make a few changes in some of our most basic components and those changes were immediately implemented throughout the entire system and on all the pages that relied on them. All of our mockups were immediately out-of-date, but that was all right, because we had something better than mockups: working pages. It took us only minutes to implement a huge, sweeping change that, had we approached this in the traditional way, would have easily blown up our timeline and budget. We’d reduced the cost of change from overwhelming to trivial.
After launch, my work focused on maintaining and expanding the design system. Eventually, my colleague left, and I became the UX designer, but I still worked with the design system directly more often than with any traditional design tool. We had reached a point where the usual underlying logic that “it’s cheaper to catch a problem in design than in production” had been flipped on its head. I could produce a fully-functional HTML prototype in a fraction of the time that even the most skilled Figma users could produce a mockup. I would regularly present a prototype in a meeting, get feedback, implement that feedback, and show what the result would look like, implementing updates live in the meeting at the pace of the conversation itself.
I worked with one developer who was rather critical of the design system because he felt that it should work like Bootstrap, empowering him to make design decisions without having to consult a designer. He asked me once how to make text red. I told him that the system had no way of doing that; it dealt with semantic markup, so to make the text red you would need to explain what that text is in terms of its semantics, and then we could change the system so that text like that could be rendered in red. Once he explained why he wanted red text, it became clear that red text was actually not the best solution, and there was an existing component that accomplished his goal more effectively. He saw this as a failing, but I saw the way that the system required developers to consult with design when making design decisions as one of its greatest strengths. It provided guard rails that could often prevent bad design from happening.
We worked with a vendor who showed us the system’s breaking point, though. They resented having to work with the design system in general, and they particularly resented being forced to observe progressive enhancement. They had their own designers who worked without consulting us, and we were forced to push their designs into our design system for them. Our usual approach of requiring a clear rationale broke down. When I asked what the use case for a given component was supposed to be, they told us that it needed to be in the design system because that was what they had designed. I ended up including several components where the only sensible guidance was that it should never be used under any circumstances, ever. The guidance went on to break down all of the situations where it might appear useful, and pointed to which other components would work better in each of those cases, but they still existed in the design system. I learned that however good a design system might be at the outset, it can only remain good if the governance around it respects its purpose and coherence.
System-Driven Design
I call this approach “system-driven design” to draw a parallel to test-driven development. It’s not quite as formal as TDD, where I can express it in a simple algorithm or even a few terse rules, but it has some key principles:
- Start with a working prototype. Your first move is to create a new HTML file, not to load Figma or any other design tool. You’ll load the CSS and Javascript from the design system and start dropping in the markup for components documented within it. When you start, you don’t have a design system yet, so you’re not loading any CSS or Javascript at that point — you’re just creating an empty HTML file.
- Make the system that will make the experience. Particularly when you first get started, you’ll probably need help from some front-end (and perhaps even some back-end) developers. Setting up a new design system can be daunting, but ultimately this system will be the focus of your work as a designer, so take ownership of it early on. You’ll be adding new components to the system, coming up with variations on existing components, and above all thinking through the logic of these components: what makes them work, why, when to use one instead of another, and documenting that reasoning.
- Solve general problems instead of specific ones. Bad design is the most recognizable symptom of a system that needs more work. If two components aren’t fitting together properly, that means that one or the other should be changed. Maybe it’s as straightforward as making the component a little smarter, so it understands more about its context. If it should have a margin of 2em above it in one context but not another, is that specific to one component, or is it actually because that component belongs to a particular class of components, and it should have that margin with any component in that class? The problem that you’re seeing is specific, but what’s the broader, more general, more abstract level where something’s wrong? Don’t focus on just fixing the problem in front of you; understand why the system produces this outcome, think about what a better system would be, and then make the necessary changes to turn this system into that one.
While, admittedly, I do feel that my own design work has been greatly improved by knowing how to code and that most designers would likely benefit from learning to code (in much the same way that we benefit from learning just about anything), I won’t go so far as to say that designers must know how to code, and I’m not asking you to do that here. All you really need to know for this is HTML. HTML is the most basic building block of the web and deserves far more respect than it gets, but “programming language” is not in any way a synonym for “valuable skill that deserves respect,” and HTML is not a programming language. I taught myself HTML in two hours one evening years ago when I was in high school. The very first Book Apart, HTML5 for Web Designers, is still in print, and at a count of just 87 pages (including the index) of Jeremy Keith and Rachel Andrew’s light, breezy prose, it can give you in the course of a pleasant Sunday afternoon a foundation that, frankly, I wish most of my front-end developer colleagues had.
In test-driven development, developers write the test first, and then write the code that will make the test pass. In system-driven design, designers work on the design system first, and then make working prototypes that use those components.
In test-driven development, the unit tests allow developers to make changes with confidence, knowing that they haven’t broken anything somewhere else. In system-driven design, the design system allows designers to make changes with confidence, being able to see what those changes will look like and reason out the implications of their decisions.
Test-driven development doesn’t shackle creativity, it just asks that you specify what you’re hoping to achieve before you start. System-driven design doesn’t shackle creativity, it just asks that you reason out how your idea fits into the system as a whole before you start.
Test-driven development allows developers to use evolutionary design. System-driven design allows designers to use evolutionary design.
This approach brings with it some radical implications for how design work is done. It doesn’t change the importance of design; arguably, it underlines its importance even more, moving it from mockups that are so easily dismissed as “making things pretty” to something that is much more obvious to non-designers as directly addressing the critical questions of what experience we’re delivering to users. It decouples your value as a designer from your proficiency with Figma and demands more clear, rigorous thinking about why you design things the way you do. It may require us to work more closely with front-end developers (something we honestly should’ve been doing all along).
If you were looking for a career where you wouldn’t have to deal with change, though, I have some bad news for you: you made one of the worst decisions in your life when you chose to become a designer. We can’t get away from it. The landscape of digital products is constantly changing, and our role within it will keep changing as well. Design isn’t about our tools, it’s about the experiences we craft for people. Change is integral, inescapable, and ever-present. It’s long past time that we changed our relationship with it. We’re in for a dire existence indeed if we see such an unavoidable part of our career as a risk that we need to mitigate and manage. We’ll be far happier and more successful if we can learn to embrace it instead.
Embracing change with system-driven design was originally published in UX Collective on Medium, where people are continuing the conversation by highlighting and responding to this story.