I've seen many design systems bend over backward to ensure styles don't leak in or out of components. Such efforts and limitations are unnecessary and are a misunderstanding of the expectations for design systems.
UI Elements Do Not Live In a Vacuum
It's fair to say that all websites (all GUI really) are designed to display a uniform and consistent interface to the user. A button on one page, for example, looks just like a button on the next page. The same goes for typography, spacing, colors, and every other UI bit. And it's not just page-to-page. A UI element like a dialog that has a button inside it should have that same button. Consistency makes the experience learnable and instills user confidence. This is human-centered design 101.
The need for consistency is true whether you're using a design system or not. In fact, that's the primary reason teams choose to use a design system, isn't it? To ensure alignment with the company design language, engineering teams incorporate a design system.
Creating a design system that assumes it could be used in a context where other competing design languages exist, i.e. conflicting CSS, is a major over-correction. That isn't to say some apps don't ever become a patchwork of differing styles, because unfortunately, they do. If a design system needs to support such an unintended situation, then highly-contained components make sense. However, that is not only a rare situation but a universally rejected one. Architecting a design system based on that rare potentiality should be avoided.
Developers know the goal is a consistent UI and they expect the design system to deliver it.
Developers also know deviations from the design language should be avoided, so again they expect and rely on the design system to deliver for all areas of design.
For example, when a developer needs a UI component like dialog or tabs, they go to the design system for it. The same is true for something simple like
<p>. They expect something somewhere in the design system will be aware of paragraph elements and it will do whatever it is supposed to do with them. They do not expect to have to build out the styles for these things or to have to reach for an additional UI library to fill gaps. The complexity or size of a UI element is irrelevant. The design system is expected to deliver on all things design, including styles for paragraphs. Hence, global CSS.
If a design system has gaps, like no support for paragraphs or links, then it fails to meet developer expectations and puts the UI at risk of deviating from the design language, which defeats the primary purpose of a design system.
This means the design system has three implementation choices:
Provide proprietary alternatives to HTML elements
Recreate HTML elements as Web Components
Support HTML elements
Let's look at each.
After React, design systems started using React to create proprietary, React-exclusive UI elements. This is still the status quo today. React (and similar frameworks) enabled developers to define much more meaningful and powerful component APIs compared to the older class-based pattern.
In practice, this meant the design system was now dictating the application's tech stack and these React design systems were getting much bigger than their predecessors. This approach to design systems has since resulted in the near abandonment of HTML altogether as teams even recreate things as basic as a div with React.
Reimplement HTML As Web Components
This approach is a carry-over from the React design systems. The tech stack has changed, but the thinking has stayed the same.
Many Web Component-based design systems are reimplementing core HTML elements like button, checkbox, dialog, input, select, and textarea as Web Components. These custom components usually attempt to copy an HTML interface (same attributes, methods, events, etc.) and map it to and from the real HTML element below, i.e.
<x-button disabled> creates
<button disabled> in the shadow DOM. Some intend to match the standard interfaces but fail to do so completely, while others intentionally deviate from standards. Both are self-defeating and confusing for developers.
Reimplementing standard elements has these downsides:
Developers cannot use their HTML knowledge
Developers have to learn and build trust in these elements despite being advertised as having the same interface
There will be cases where the custom implementation is missing a detail and developers have to hack in support for it or go without or wait for the design system to add support for something that HTML could already do
There will be cases where the custom implementation deviates on purpose, like changing the default button type, and developers don't realize it and ship bugs or they have to do extra work to get normal behavior
Worst of all, these abstractions have to be proactively updated with the web platform specs forever. If they are not, then developers are locked out of new web features. Think about it: three years from now will these design systems have added support for any and all relevant additions to the web platform, or will apps be stuck in an almost IE-like situation where the web platform has progressed but the design system has not? React, for example, has millions of dollars and a team of full-time maintainers and it has taken four years and counting at the time of writing to officially add support for HTML's new
The desire to reimplement HTML elements as Web Component abstractions is based on a misunderstanding of the expectations discussed above. This is an anti-pattern and should be avoided.
Support HTML Elements
This option offers the most compatibility, the most flexibility (but still within the control of the design system), and the most longevity. This also means using global CSS in the design system because paragraphs are just as much a part of the system as dialog or tabs.
Web developers know and expect to use HTML. The declarative style of the language and its inherent composition is quite powerful. And today it offers many of the features developers have been asking for over the years. Supporting HTML elements as-is means developers get the following benefits exclusive to this approach:
Previous experience with the web platform is immediately applicable to the design system, e.g. I know how this design system's checkbox works because I know HTML checkboxes
Trust and confidence are instantly established
Guaranteed to be bug-free
Results in the smallest design system assets possible
New web platform features are available in real-time automatically forever
It's worth mentioning the developers who manage such a design system have an enormous maintenance burden removed by this approach.
Now, we all know HTML doesn't have an element for everything. There is no badge or icon element, for example. If a design system shouldn't have gaps, then how are those implemented? With Web Components. This is the reason for Web Components.
But do we have to go full Web Component for things as simple as an icon or badge? Seems like overkill just like reimplementing HTML elements is overkill. Agreed. I'll close on that topic.
How To Do Web Components
I have been on several design system teams at companies large (20+ globally distributed teams building dozens of apps, two times doing that) and small (very early stage startup launching one greenfield app) and I made many mistakes along the way. Having learned from that experience, I can say what worked well for those teams is:
Design systems should be a helper, not a rigid dictator
everyone knows it
requires the least maintenance
stays fresh longer
smallest asset sizes
Documentation with lots of guidelines and samples is highly appreciated
And the biggest lesson of all is what ultimately became the TAC CSS methodology, which is like progressive enhancement for Web Components