CSS occupies an interesting position among web technologies: while it can appear almost quaint in its simplicity, it’s also been interpreted by some as the most vexing language in web development. Despite its approachability, CSS sometimes gets a bad rap — one that I believe derives from a fundamental misunderstanding of CSS’ history, evolution, and function as an API for styling on the web.
In this article, we’re going to review some of that history and evolution. We’ll then use that information to inform a focus on several methodologies — some battle tested, some more recent — for making the process of styling web apps and components both enjoyable and effective.
The origins of CSS
In order to gain a deep understanding of CSS, it’s important to first understand the ecosystem from which it emerged: that is, the early days of the World Wide Web. Getting familiar with this context is essential to understanding why CSS works the way it does — and also provides some insight into just how far it has come since its inception.
In contrast to the dynamic and interactive nature of the Web of today, the Web started out as a comparatively simple medium: that is, one for publishing documents. This intent was clearly stated on , at the hands of Håkon Wium Lie and Bert Bos. At the risk of oversimplifying things, at its highest level, this first draft of CSS could be reduced to three fundamental tenets:
CSS is a language for authoring style sheets for HTML documents.
CSS encourages the independence of markup from style sheets, thereby preserving content fidelity and structure, while allowing for the application of reusable styles.
CSS’ style sheets cascade — that is, styling rules declared by the user agent may be overruled by styles declared by the document author, which themselves may be overruled by the end user.
This third tenet — the cascading nature of CSS — was a source of great debate at the time, and traces of this debate even carry on amongst web practitioners today. And yet, CSS’ cascade is perhaps one of its most defining attributes: it underscores the web as a medium where content and its presentation is informed not only by browser makers, but also by content authors, end users, and those users’ devices and their capabilities. Even from the Web’s earliest days as a platform for static documents, CSS was, in its own way, declaring in no uncertain terms that the presentation of content on the Web must be approached not dictatorially, but democratically — or, to use a more modern and technical term: responsively.
The second tenet, referencing CSS’ global scope and its independence from HTML, will of course be a familiar topic to anyone who has touched frontend development over the past two decades. We’ll get deeper into this later in the article.
The first tenet I’ve proposed above, however, is perhaps the most impactful, yet also the easiest to overlook. In fact, I believe the nature of this principle is one that a great many web developers to this day tend to forget (or never learn in the first place), and this in turn has become a source of some of the deepest struggles in frontend web development over the years.
And so, let us spell it out clearly: CSS, as game changing as it was, was not created as an application or component styling API — it was designed as a means of styling static documents, authored in HTML. Documents and applications (and components), however, present drastically different contexts for design. The nature of the standardized Web, meanwhile, as one of (perhaps the most)
Photo by , devised by , represented a fundamental rethinking on the composition of were often at odds with team dynamics. Style collisions became increasingly common, where changes introduced by one developer would inadvertently affect styles elsewhere on the website. As the old joke goes: two CSS properties walk into a bar; a bar stool in a completely different bar falls over. As these issues and the number of people experiencing them multiplied, so too did new CSS methodologies, particularly those focused on style sheet architectures. Before long, we had , , and more. Third party supersets of CSS also appeared during this time, such as , which gave style sheet authors access to scripting features like variables and loops.
The extent to which CSS supersets benefitted or hindered the progress of styling on the web is debatable. Sass, for example, should be credited for introducing variables to CSS, which in turn inspired CSS’ own declares that classes should be named based on a hierarchy derived from both markup and state. This strategy introduces a dependency between the structure of a page’s markup and its styles, a strategy CSS itself had attempted to avoid.
BEM is not the only methodology to use this kind of ruleset format — many other methodologies rely on markup context (or content context) to inform the construction of classes. Herein lies the problem, though: while this approach could be said to encourage pleasant developer ergonomics, the results are inherently brittle (due to the tight coupling between markup and styles). On top of that, the prioritization of selector nomenclature above the actual styles being applied to those selectors often results in style sheets that are bloated with repeating property declarations — see for example these styles from the Financial Times’ website:
.o-ads--label-left .o-ads__inner:before {
content: "▼ Advertisement ▼";
display: block;
font-size: 14px;
text-align: "left";
}
.o-ads--label-right .o-ads__inner:before {
content: "▼ Advertisement ▼";
display: block;
font-size: 14px;
text-align: "right";
}
.o-ads--label-center .o-ads__inner:before {
content: "▼ Advertisement ▼";
display: block;
font-size: 14px;
text-align: "center";
}
.o-ads--label-with-borders {
font-size: 14px;
text-align: "left";
}
In these ways and others, many of the aforementioned methodologies could be said to work against the grain of CSS, despite their intent to make styling easier and more robust. As such, the process of writing and maintaining CSS in the mid 2010s had become increasingly complex; but it also set the stage for a radical rethinking, and a move towards simpler, more efficient, and more resilient methods of styling content on the web.
The atomization of CSS
For many years, the semantic nature of HTML led many to proclaim that CSS should also be written ‘semantically’. However, this tight coupling between HTML semantics and CSS selectors, despite being recommended even by the W3C as a best practice, does not have a basis in reality. , spelled this out quite clearly in 2012:
The primary purpose of a class name is to be a hook for CSS and JavaScript. If you don’t need to add presentation and behavior to your web documents, then you probably don’t need classes in your HTML.
In the absence of a mandate to describe particular nodes of content or . At the heart of Koblentz’s article was a well-argued overview of how so-called ‘best practices’ in CSS at the time often lead to multiple rewrites of both CSS and HTML whenever UI requirements change (that hardly ever happens, right?), leading to ever-growing, ), but what Koblentz and others were proposing — generally referred to as ‘atomic CSS’ — took this approach to the logical extreme.
To illustrate the drastic difference in approaches, consider the following two implementations of the media object (for simplicity, implemented with flexbox):
<!-- ‘Best practices’ media object -->
<style>
.media {
display: flex;
}
.media-img {
flex-shrink: 0;
padding-right: 8px;
width: 128px;
height: 128px;
}
.media-content {
flex-grow: 1;
}
</style>
<div class='media'>
<img class='media-img' src='…' alt='…' />
<div class='media-content'>
Here’s a traditional media object.
</div>
</div>
<!-- ‘Atomic’ media object -->
<style>
.flex { display: flex; }
.flex-shrink0 { flex-shrink: 0; }
.flex-grow1 { flex-grow: 1; }
.padding-right2: { padding-right: 8px; }
.width6 { width: 128px; }
.height6 { height: 128px; }
</style>
<div class='flex'>
<img class='flex-shrink0 padding-right2 width6 height6' src='…' alt='…' />
<div class='flex-grow1'>
Here’s an atomic media object.
</div>
</div>
Note how each class in the atomic version maps to just a single CSS property and value. In fact, if I hadn’t included the second <style> block, I bet you’d have had no problem determining each class’ effect from the markup alone! This is a hallmark of atomic CSS — the effect of a class is typically self evident from its name alone, whereas the specifics of a class name like media are more ambiguous.
For anyone familiar with atomic CSS today, the example above will likely appear unremarkable. The transition towards this approach was anything but, however — and on some corners of the web today, debate still rages about whether atomic CSS has been the best or worst thing to happen to styling on the web since CSS.
There was, however, clearly an appetite for this approach amongst a non-trivial swath of web developers: the year 2014 saw the release of both Adam Morse’s , the first two frameworks to go all-in on atomic CSS. These frameworks were instrumental in writing the blueprints for the atomic CSS methodology and turning the status quo on its head — and indeed, the shift was so monumental that, within a number of years, ‘utility-first’ CSS frameworks started becoming , hence the alternative name ‘functional CSS’. Additional inspiration came from becomes an advantage as it was originally intended: compositions can be built up with inheritance in mind, over several layers of markup.
There are, however, many common objections raised against the atomic CSS methodology. In general, these tend to be:
- ’It’s not semantic.’
- We’ve touched on this already, but it’s worth repeating: semantics, accessibility, and clarity do matter, but with all due respect to . Iterating in this fashion simply cannot be matched when using other methodologies.
- ‘This is so not is one of those articles that changed the way I thought about web development when it was published, and there’s no denying that building frontends on the web has become a component centric process. However, it’s important to differentiate the process of thinking in components and the process of styling components. A conceptual abstraction does not require an equivalent material abstraction, and the fact of a component’s existence does not necessitate a dedicated CSS class.
- ‘This still doesn’t solve the problem of global scope or one off styles.’
- It doesn’t, and in fact atomic CSS is not designed for this. For scoped or one off styles, a different approach is absolutely required.
Atomic CSS can provide a fantastic foundation that covers the vast majority of styling needs for a given website and its constituent components, and it can deliver those styles in a fraction of the file size and complexity of other methodologies. To be clear, these claims are not theoretical: this has been my experience both as a contributor and leader of frontend teams over the past 8 years, and the same has been true for many others both within and outside of my professional circle. But as we’ve noted, atomic CSS doesn’t cover every use case: scoped and one off styles are not part of its wheelhouse. So what’s to be done when a need for these sorts of styles emerges?
Going bespoke
Where one off styles are needed, or where we want to ensure certain styles are scoped to a given component, additional measures beyond an atomic CSS methodology will be required. There are several techniques that can be used to address these concerns, with a few notable examples having become more popular in recent years:
- CSS in JS
- The obvious contender in this list. I used CSS in JS for many years myself, and have to say the developer ergonomics are pretty impressive, as is the ability to leverage both repeatable and bespoke, scoped styles (especially when using libraries like ). Unfortunately, great developer ergonomics and scoping are not enough. CSS in JS can add significant weight to client side bundles, along with increased setup complexity (especially when server side rendering is involved). Some solutions can also lock you in to certain frontend frameworks, limiting the portability of your styles. There are some solutions emerging to address these concerns (e.g. should address this in the future.) Further, along with Shadow DOM and HTML templates. I may be biased, but I think the best way to work with custom elements right now is with (SFCs) has a number of huge benefits:
Custom elements are expanded on the server, providing great performance and an excellent baseline for progressive enhancement on the client.
Locally scoped, one off styles can be authored simply by including a
<style>block in your SFC. When your component is expanded on the server, these style blocks will be hoisted into the document head, with all of that style block’s selectors scoped to your custom element. This allows for one off styles to be encapsulated and scoped to the component they’re authored in, without needing to touch the Shadow DOM. Scoped styles written within an SFC are also a great place to leverage strategies like . This is particularly handy for engineers (or designers) who might excel at HTML and CSS but lack experience in JavaScript. Although SFCs are authored as JavaScript functions, the bulk of the authored code is written in HTML and CSS, as seen below:
CODE// my-button.mjs
export default function MyButton({ html }) {
return html`
<style>
/* One off styles applied only to button elements rendered by MyButton. */
/* Any button outside this component will not be affected. */
button {
appearance: none;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
</style>
<!-- Atomic classes used for repeating styles -->
<button class='p1 radius-pill cursor-pointer'>
<slot></slot>
</button>
`
}
// index.html
<my-button>Click Me!</my-button>
Of course, one need not use Enhance to gain the benefits of using custom elements. Being , which can be easily customized to integrate with design systems or brand guidelines. Enhance thus presents an end to end styling solution that offers all the benefits of atomic CSS as well as the power to easily create one off, locally scoped styles on a per component basis. (You may have also realized that, since SFC styles are authored within a JavaScript file, those style blocks can also take advantage of some CSS in JS niceties — such as leveraging JS variables or functions — without authors having to worry about client side performance. While I’ve yet to find much of a need for this, the possibility is there.)
Summing up
We’ve covered a lot of ground in this article — some of it historical, some of it subjective. Although I’ve used a lot of words to describe the benefits that I and many others have encountered with atomic CSS in comparison to other methodologies, I do want to assert that, as with so much on the web, your mileage may vary. Technical methodologies of all kinds inherently attract certain folks and repel others, and
- Let's Talk About Web Components
↗ Original-Artikel auf dev.to lesenVollständiger Original-BerichtAusführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
SOCIAL SHARE CARD GENERATOR