14 June 20246 minute read

On Headless Component Libraries

Prologue

The inspiration for this post was this tweet by James Sann. Below is an excerpt.

"...highlight the pros and cons of using third-party headless web UI systems like MUI base or Radix for foundational components vs rolling your own..."

If you're here for the TL;DR of this post, it's this: Relative to the number of design system practioners, exceedingly few should build a component library from scratch without using some sort of headless library as a foundation. The drawbacks of being tied to an external dependency are far outweighed by the benefit of getting to critical mass more quickly and ensuring component behaviors are well-defined, consistent, and accessible.

Headless UI & Democratizing Design Systems

I think it might help to start with some context. I maintain the infrastructure for Canvas, our design system. Part of that is Canvas Kit, our component library, which turned seven years old this week! 🥳 As of today, our team of six engineers support an ecosystem of around 120 active repositories and hundreds of engineers. Canvas Kit is very much a custom component library. We are fortunate to have a mature library and ecosystem with enough support to build most of what we need in-house.

If there's anything I've learned during my time on this team, it's how incredibly expensive component libraries and infrastructure are. But one of the things I'm most excited about in the DSE space is the democratization of tooling. 10+ years ago, you had to be at the scale of Google, Adobe, Saleforce, or Twitter to be able to build a large, robust, and effective design system. But there's been a seismic shift. Today, small scrappy teams are able to use best-in-class tooling to build scalable systems. What's more, they're nimble and can iterate more quickly than large ecosystems. That's a massive win for the design system community.

I see headless component libraries as a big part of this movement. These libraries are solving common but very difficult problems with solid APIs and are building communities around their projects. Their efforts have the potential to transform the web into a much better place, and I hope they are wildly successful. There are many in this space, but I'll list the ones I personally like and respect the most:

Getting to Critical Mass

When you begin to implement a design system, assuming you already have a good start on your visual language, your first job is to get to a critical mass of tokens, components, specs, and docs for teams to be able to consume your system. You don't need to have everything, but you need enough for a team to either build or rebuild most of their product UI with what you have available. For components, that means you need a solid collection of containers, buttons, inputs, indicators, etc. to get started. Getting to critical mass is key because before you hit that point, none of your work matters. You might one day become what all design systems aspire to be: A scalable accelerator, a productivity booster, an experience enhancer, and a cost saver. But until you hit critical mass, you will be viewed as a cost center, and cost centers always have an expiration date.

Anyone who has built a custom component library will tell you getting to that critical mass is not a straight shot. The path is winding and fraught with error. Whether you decide to use a headless UI or build from scratch, you have to hit critical mass to start showing real value to the rest of the business.

Battling Browsers

Building components such as cards, buttons, and links requires considerable thought, but the implementation is typically pretty straightforward. Things start to get trickier with inputs and other interactive components. One of the major problems building on the web today is how difficult it is to create even the most common interactive components. Styling a native <select> is impossible, and building a custom Select input is incredibly challenging. Styling checkboxes and radio groups is possible, but it's still very involved. And again, these are very basic and well-defined inputs. When you start working on color pickers, multiselects, drag-and-drop, and other complex interactions, things get much more complicated to manage. Component behaviors and clear accessibility guidance might not even exist.

Beyond components, you'll also need to create effective subsystems to govern their behaviors such as programattic focus, cursors, lists (both visible and virtual), pop up positioning and order, and modality to name a few. These are very difficult problems that require considerable focus to solve well. I hope there will be a day when this functionality is baked into browsers. But today, it's all on engineers to implement.

You don't need to solve all these issues to reach your critical mass and start showing value, but you'll probably run into them sooner than you think. And at that point you have choose from a few options:

  1. Avoid adding the functionality and pass the complexity off to teams
  2. Create a custom solution
  3. Use a third-party library to handle those problems for you

You might be able to get away with option 1 and pass off the complexity to teams, but doing so also diminishes the overall value of your library. Option 2 increases the library's value, but is also the most expensive choice. You'll need to think about the cost of maintaining and supporting your custom solution. Option 3 requires you to add a dependency, which is a real cost to consider, but it's likely not as expensive as option 2, and it creates more value than option 1.

Remember, at this stage in the design system game, you're not in the "we build everything in house" game. You're in the "we need to provide real value as soon as possible" game. With that in mind, it's hard for me to argue against option 3 for most teams.

Styling

The main detractor for most of the early generation of component libraries was styling. "Choose Bootstrap, and everything looks like Twitter. Choose Material, and everything looks like Google." Style overrides were hacky or impossible. It's painful to fight your own components when you're trying to get a new visual language off the ground. But headless UI libraries solve this, and perhaps that is their greatest strength. Instead of fighting styles tied to some other visual language, you are free to apply your own.

Considerations

Being dependent on external open-source libraries is a risk, and something to carefully consider.

  • You're tied to a release cycle you don't own.
  • You could be subject to arduous upgrades.
  • The project could lose support and die off.
  • Teams could balk at the idea of yet-another-dependency
  • Teams could complain about the added bloat to their bundle.

Those risks are real, and you'll need to have answers for them. How you choose to proceed is going to depend a lot on your particular circumstances. I am amazed at how many small design system engineering teams exist today. The tooling ecosystem continues to improve, allowing these small teams to build more scalable solutions and support even larger ecosystems. I see headless UI libraries as a significant part of that, solving big problems without the styling issues of previous libraries. They can help expedite the effort to show real value.

But the best reason to get past the critical mass phase, IMO, is because that's where the really interesting design system problems are found. You'll always have to prove that your design system can provide real value and should continue to exist. But instead of getting enough of the right components in place, you'll need to think about how to optimize all the other systems that connect teams to your design system. You'll find new ways to increase the impact of your work far beyond what adding a new component can do.