DEV Community

Cory Rylan
Cory Rylan

Posted on • Originally published at coryrylan.com on

Resilient CSS APIs and Design Systems

Design Systems and UI libraries help companies and product teams align visual design consistency and speed up UI development. As much as we hope to make our UIs pixel perfect, many aspects of the Web actively work against pixel perfection. Not only do we have to deal with browser inconsistencies but also other code running in the application unintentionally breaking or changing our expected visual output.

Many Design Systems and UI libraries use global CSS to apply the website’s desired look and feel. Global CSS can be good and bad in different use cases. Global styles can help easily make things consistent but also cause conflicts. Let’s walk through a seemingly simple scenario.

Our Design System needs consistent styles for our h1 headings. We define something like this in our code:

h1 {
  color: #2d2d2d;
  font-size: 24px;
}

Enter fullscreen mode Exit fullscreen mode

Great, now every h1 heading will look the same. Quickly we run into issues at scale. What if we want to use an h1 style but can’t due to an h1 existing already on the page? Duplicate headings are bad for SEO and accessibility. Well, we could make an h1 class.

h1, .h1 {
  color: #2d2d2d;
  font-size: 24px;
}

Enter fullscreen mode Exit fullscreen mode

This additional selector is a step in the right direction. We can now use any semantically correct element to style it like our system’s h1 style. But this is still at risk of other issues. First, if we are building a Design System to support multiple apps or teams, they may be already using another library to style their app. By applying styles to native element selectors, we have a high risk of causing style collisions.

.h1 {
  color: #2d2d2d;
  font-size: 24px;
}

Enter fullscreen mode Exit fullscreen mode

So if we remove the native element selector, now we eliminate the risk of the Design System colliding with other styles. This now leaves the question of why call it an h1? Why not name it to something semantic that makes it easy to understand its purpose?

.heading {
  color: #2d2d2d;
  font-size: 24px;
}

Enter fullscreen mode Exit fullscreen mode

That’s better, but the class name is a bit generic and still could be more specific. We can prefix with some standard name for our system.

.ds-heading {
  color: #2d2d2d;
  font-size: 24px;
}

Enter fullscreen mode Exit fullscreen mode

The prefix could be a shorthand name for the product or company. Here I have ds for Design System. So now, we have a scoped isolated and consistent way to style headings in our design system. Something as simple as a class name can be more complicated than it seems. Thoughtful API design can save teams significant amounts of time when trying to adopt, use, or upgrade to your Design System/UI Library.

Often I think CSS selector types are often not used to their potential. While CSS class names are the most common style hook, we can use HTML attributes to provide a more expressive CSS API.

[ds-text*='heading'] {
  ...
}

[ds-text*='subheading'] {
  ...
}

[ds-text*='body'] {
  ...
}
Enter fullscreen mode Exit fullscreen mode

<h2 ds-text="heading">A cool heading</h2>

<h2 ds-text="subheading">A cool subheading</h2>

<p ds-text="body">A cool paragraph</p>

Enter fullscreen mode Exit fullscreen mode

By leveraging attribute selectors, we can may our CSS much more expressive. Not only is the style selector specific and scoped but also easily conveys its intent in the HTML.

CSS libraries and Design Systems can leverage more expressive selectors and Shadow DOM to create resilient CSS that works in more predictable and reliable ways. Check out the Clarity Design System Typography utilities, as well as blueprintcss.dev which both leverage similar attribute-based APIs.

Top comments (0)