Somewhere buried in my dusty basement lives a signed copy of Dan Cederholm's classic book Bulletproof Web Design. I purchased it during my first trip to An Event Apart in 2007. This book meant a lot to me back then, and almost 15 years later, many of its concepts still hold true.
The premise of the book is that designs should be "bulletproof" to accommodate the many ways they can be used.
I'm using the term bulletproof partly to describe flexibility—in other words, designs for the web that can easily accommodate various text sizes and amounts of content, designs that expand or contract along with whatever is placed within them.
I've been a software engineer for many years. More recently, my focus has been on building design systems. Although a lot has changed on the World Wide Web over the last decade, it's easy to recognize that many of the principles in Dan's book also apply to component development.
But what does a bulletproof component entail? Before jumping in, let's take a look at why we break down websites and applications into components in the first place.
Perhaps the most obvious benefit of the component pattern is reusability. Nobody wants to write the same code over and over again. If you end up doing that, have fun finding and updating all of the instances — and variations thereof — that make their way into the wild. This doesn't scale well in small orgs and it falls flat on its face in large ones.
Components are a vehicle for reusability. Their very nature leads to visual and behavioral consistency. By encapsulating structure, style, and logic into neat little packages, components become powerful tools in your users' arsenal — tools that are convenient to use and a bit harder to misuse.
When you break a design down into reusable bits, you can assemble, or compose, those bits to recreate the original design. Better yet, you can create entirely new designs with a lot less effort than building them without composition. Components are the building blocks of your application's foundation.
It's no secret that I'm a fan of web components (and I'm in good company), but I'm not here to sell you on web components today. What I will say is that more and more large organizations are leaning on web standards to build and rebuild their design systems in a technology agnostic way. Interoperability falls under the umbrella of reusability, but I think it's an important technical detail to point out.
The term bulletproof might be misleading, so let's consider this passage from Dan's book before we continue:
Out in the nonvirtual world, a bulletproof vest never guarantees complete, 100% protection, but rather being bulletproof is something that's constantly strived for. You're far better off wearing a bulletproof vest than if you weren't.
What we're aiming for with bulletproof components isn't perfection. We're aiming for components that are as intuitive, flexible, and resilient as possible.
Here are some of the key features that comprise a bulletproof component.
Every component has an API that serves as its primary interface for users. As a component author, many of your users are probably developers which means good DX is critical. Offering consistent, predictable APIs is vital to good DX and therefore a fundamental feature of bulletproof components.
If you have a library, for example, don't make the
size property accept
small|medium|large in one component and
sm|md|lg in another. As a user, if I've used one or two of your components, I should be able to get the hang of things pretty quickly based on prior experience. Let me build on that knowledge as I go.
Similarly, I should be able to glance at any component and have a pretty good idea what it does. I know you've documented it, but when I see a component in the wild, I don't want to open the docs unless I absolutely have to.
It's easy to overload a component with too many features. Oftentimes, that's a sign you should break it down further. Components that take on too much responsibility aren't only harder to use, they're harder to maintain too.
Components are designed to be reused, so you never know where they'll end up in an application. Plan for this. Expect that users will drop your component into a 100×100 container, a 1200×1200 container, and everything in between. It's not that they're trying to break things, it's that edge cases happen a lot and it's better to be prepared than to be caught off guard.
As component developers, we can't predict every possible use case, but we can offer resilience by asking the right questions during development.
- Does it break when I place it in common locations?
- Does it grow and shrink as the user will expect it to?
- How does it render on a mobile device? A tablet? An extra large screen?
- Is it accessible in all places it's likely to be used in?
- Does it work equally well on touch devices as it does with a mouse?
- Does it work properly as part of a composition?
Sometimes it doesn't make sense to test for one thing or another. Use your best judgment and consider the intent. Nobody understands your users better than you, so adapt your questions accordingly.
As building blocks, components must work across a variety of applications. Unless your organization has a technical mandate, this usually means you need to support a variety of frameworks such as React, Vue, Angular, etc. Interoperability doesn't mean building the same component for every framework you need to support. It means using the same exact components in every framework.
This might sound like a web component plug but, realistically speaking, why wouldn't you want your components to work everywhere? Interop is important because there's a lot of upfront costs, maintenance burdens, and technical debt that comes with building the same thing multiple times.
Smaller orgs can get away with using a single framework for awhile, but it will eventually catch up to them. Which brings me to the next feature of bulletproof components…
Congratulations! You've built a component library using your favorite framework. Your users are happy and everything is great. They love the shiny new buttons you gave them!
[a year or two later]
Oh look, your favorite framework has evolved. Version 2 is out and that means breaking changes! Quite possibly lots of them. No worries though, version 1 is still, uh, kinda supported. You can probably keep using that for awhile. Wait, what's that? You say a number of teams in the company have already upgraded to version 2?
Now you have two component libraries to build and maintain! Or maybe you can endure the challenge of getting everyone in the org to upgrade their old apps. Neither situation is ideal for you, your users, or your organization. Web components can help mitigate this.
As a component author, I can tell you without question that most developers don't like to read documentation. Nevertheless, good documentation is a necessity for bulletproof components. But what exactly is "good documentation?"
Good documentation isn't necessarily comprehensive documentation. While it's certainly important to include all the relevant details of a component's usage, producing superfluous amounts of content is a disservice to your users. People aren't reading your documentation during their coffee break. They have work to do and they want to get it done efficiently. Make it easy for them to find what they need.
Your component's documentation should be scannable. Tell me what it does. Tell me how to import it. Tell me what props are available. Fill the page with relevant examples and order them from most common to least common, leaving the edge cases for last. Call out important tips and gotchas in context so they're prominent, but not distracting.
Don't let your content become stale. Nothing is more frustrating than docs that aren't aligned with a component's implementation. Avoid decoupling things that are likely to be forgotten over time.
Code analyzers can help keep important things such as props, events, and more up to date by bringing the documentation closer to the to code. Remember how I said developers don't like to read? Most don't like to document things, either. Make it harder for them to not document their changes.
Building components is an art, and this post certainly isn't definitive nor comprehensive. My intent is to document, through experience and observations, the characteristics that make up bulletproof components.
Consider this an ode to Dan's vision of Bulletproof Web Design — an extension of a great idea adapted for component development.
I'm particularly proud of the work I've done on Shoelace, a library of components that I purposefully maintain with longevity in mind. This project demonstrates all of the principles I've described herein, and I encourage you to use it as inspiration in your own projects. I also encourage you to contribute to the paradigm of bulletproof components through discussion or demonstration via Twitter.
I can't wait to see what you're working on!