website/content/posts/css.mdx
2024-04-13 15:26:52 +02:00

220 lines
12 KiB
Plaintext

---
title: "CSS - How do you even write that?"
date: 2022-10-01T21:06:44+02:00
tags: ["webdev"]
---
import Sidenote from '@components/Sidenote.astro';
## Writing CSS is hard
I've heard many people say that CSS is pretty difficult to write. There are hundreds of properties to remember, some of which aren't even compatible with some browsers yet. CSS spec is certainly full of landmines and edge cases which not a single human can fully remember... and yet, I do enjoy writing CSS. Or rather, I should say that I enjoy writing SCSS, but fundamentally it doesn't change all that much.
<Sidenote>
According to the [index of CSS properties](https://www.w3.org/Style/CSS/all-properties.en.html) at the time of writing there are 158 properties with recommendation or note statuses. There are hundreds more properties with different statuses.
</Sidenote>
Throughout the years CSS has gotten better. We can now use things such as flexboxes or grids, we can create beautiful animations without an ounce of JavaScript being involved. Of course, first we have to learn all the various properties, how to combine them together to create the desired effects. However, in my experience at least, this isn't even the hardest part about CSS. I know lots of properties and complex selectors, and how to use them, and I do admit it was pretty difficult to figure out all of this stuff myself. The only thing I know for certain now is that there is no single straightforward way to *structure* your CSS.
Using Sass does help, because we can use it to nest various rules together and split them into different files. We can `@use` these files in other files. Even then, I haven't found an easy way to write CSS that scales in a bigger project. In my experience it's extremely easy to turn your styles into a hot mess - extremely fragile to any changes and difficult to modify.
## The road to better CSS
When I first started writing CSS I had the idea that CSS is all about writing styles that can be reused. Ideally, we should be able to write a small stylesheet that can easily be used for all the various parts of our websites and it should just work, right?
Sadly the reality is *significantly more difficult*. HTML is a structured markup language with strict hierarchical nature. When we write CSS we write styles that are supposed to be applied to this structure, which means that, whether we like it or not, our styles will end up reflecting the way HTML is structured one way or another. When we change HTML we can break our styles, sometimes in very unpredictable ways.
One way we could write CSS is like this:
```scss
.blog-post {
...
&.center {
margin: 0 auto;
...
}
&.tech {
...
}
header {
...
.title {
...
}
}
}
```
This might seem fairly reasonable at first, but there are a couple of problems that this might end up causing us in the future.
First thing is that we are using "modifier" classes to modify the `.blog-post` class, to give it some different look, possibly because of being used in a different context. The result of nesting rules like that is creating rules with high specificity, which are sometimes very hard to override. For instance, the `.blog-post header .title` rule has a specificity of (2, 1). We won't be able to override that style with a rule `.blog-post .title` (2, 0).
<Sidenote>
Specificity is simply a number [calculated](https://specificity.keegan.st/) from the complexity of the selector. The higher the specificity, the more priority the CSS rule has. Many people argue that we should always aim for as low specificity as possible, which means we should use as simple rules as possible (but still specific!).
</Sidenote>
Another problem is that we are using a very generic element `<header>` as a selector, which means that every single `<header>` that is a child of `<element class="blog-post">` will receive these styles. That could be a bad thing if we would like to use `<header>` element in semantically different contexts, for example as an article header and as a section header.
Third problem is that at some point we might realize that in fact certain elements don't necessarily have to be an integral part of a hierarchy of HTML nodes. We could for example want to reuse the `<header>` element in a different part of the website with the same style. If the styles of this element depended on styles of the `.blog-post` element, we would be in pretty big trouble.
## Reusability
We all would like to write reusable styles, but I honestly feel like all this talk about reusability is kind of the wrong way to look at the problem. What if we tried to write a new CSS rule for every single piece of our website anew without reusing anything? Surely, we would just end up with a colossal CSS file spanning over 6000 CSS rules with a total size of about 500 kB. We wouldn't really reuse anything. What's the opposite of that? What if we tried to reuse every single CSS rule? Well, then we would have to write a single CSS rule per every property we use, and then use these classes to assign each property individually.
<Sidenote>
The last idea is actually a thing, there are CSS frameworks out there which use this idea of "atomic CSS". One such example is [Tailwind CSS](https://tailwindcss.com/).
</Sidenote>
If we only used a single class per each property in CSS and assigned many of such classes to each HTML tag, wouldn't we just end up doing exactly the same thing as in the first idea, except now by doing it within the `class` attribute? Consider this element:
```html
<div class="flex items-baseline mt-4 mb-6 pb-6 border-b border-slate-200">
```
How is it different from just creating a class for each element and assigning properties in CSS? This whole topic is pretty difficult... Maybe there is a golden mean to this problem? Classes that are small enough to be reusable in HTML, but at the same time not devolved into singular properties?
I personally think that it is *possible* to find classes which are small enough to be reusable, but also not insignificant. However, it is possibly the most difficult thing to design, or to find, or to architect, or whatever you would want to call it, in CSS.
## To BEM, or not to BEM?
<Sidenote>
In short, [BEM](https://getbem.com/) is a naming convention which is all about writing class names in a very specific way. We write names as combinations of three parts: block, element and modifier, with the latter two being optional.
</Sidenote>
One of the things which have helped me improve the state of my CSS stylesheets is trying to incorporate BEM into the way I write my rules. I won't explain what it is exactly, because that's not the point, but I'll try to summarize my findings.
If we tried to rewrite the previous example into BEM, we could theoretically rewrite it as such:
```scss
.blog-post {
...
&--centered {
margin: 0 auto;
...
}
&__header {
...
}
&__header-title {
...
}
}
```
BEM definitely helps manage specificity, as each rule will affect exactly a single class with the selectors consisting of that single class, ie. `.blog-post__header-title {}`. By assigning an unique class to each rule we also alleviate the problem with our styles leaking and having unwanted effects; we end up writing extremely precise rules. However, BEM doesn't help us architect what part exactly we should consider a single atomic "block". We still can easily commit mistakes when it comes to the *granularity* of styles.
BEM could devolve into writing separate "blocks" for each HTML element. We could also treat the entire website as a single BEM block. So, as it turns out, we still don't know how to write styles that are *reusable* for the various parts of the website. It is entirely up to us to decide which elements constitute a part worth treating as a singular block in BEM.
One problem that I have personally encountered with BEM is not being able to figure out how to approach the fact that a single "block" can have multiple variants. For instance, we could have three cards, each containing slightly different content:
```html
<aside>
<section id="A" class="???">
Card with some content
...
</section>
<section id="B" class="???">
Card with slightly different content content
...
</section>
<section id="C" class="???">
<img alt="some image we would like to show" />
</section>
</aside>
```
Should we treat the variations of the block as modifiers indicated in BEM by the `--modifier`? But then we wouldn't be able to style the elements of these blocks, as in BEM you can't write `--modifier__element`. Should we treat these blocks as completely different elements and create a multi-class rule for the common styles? That's certainly an option. Maybe we could also create a generic class with common styles called `.card`, and then create rules for the more specific styles using different classes, ie. `.card-info`, `.card-image`? In this case we wouldn't have problems with the convention, but we would run into a different problem. Namely the class `.card-info` can't be used individually - we always have to remember to use both `.card` and `.card-info` at the same time.
Maybe the option with the multi-class selector is the most rational then?
```scss
.card {
padding: 1em;
border: 1px solid black;
border-radius: 1em;
...
}
.card-info {
@extend .card;
&__info {
margin-top: 1em;
...
}
}
```
After compilation we would have:
```scss
.card, .card-info {
padding: 1em;
border: 1px solid black;
border-radius: 1em;
...
}
.card-info__info {
margin-top: 1em;
...
}
```
## Styles in Hugo
I have found that Hugo, other than being a really useful tool for creating static websites, also helps in architecting them in scalable ways and helps demarcate boundaries between the various parts of pages. Hugo provides us with two similar mechanisms for creating reusable components - partials and shortcodes.
Partials are pieces of HTML code defined in separate files, which we can reuse in the structure of the website, by calling them like this:
```html
<aside>
{{- partial "panel" . -}}
</aside>
```
On the other hand, shortcodes are pieces of HTML code defined in separate files, which we can use only in the markdown content that we have written for the site generator.
```md
## Calendar
Here is *my* calendar:
{{</* calendar */>}}
Please take care...
```
The fact that in Hugo we can componentize parts of our websites and explicitly reuse them in various places means that we end up defining what constitutes a building block of the website. I think that this helps in creating a manageable architecture which ends up extendable and modifiable in the future.
Given that we have defined the distinct blocks, we can use BEM to style these blocks individually. For example we could write a BEM block for the panel, or the calendar:
```scss
.p-panel {
...
&__info {
...
}
&--active {
...
}
}
.sc-calendar {
...
&--collapsed {
...
}
}
```
I've added here my own convention of marking partial styles with the prefix `p-` and shortcodes with the prefix `sc-`.
So far, I've had good results with using BEM in conjunction with the Hugo partials and shortcodes. These mechanisms make sure that I always know what constitutes a block and what might be reused in different parts of the website.
One of the problems with this approach which I've run into is that it's sometimes difficult to connect the various pieces together into a coherent whole. We could have partials for sidebar, header and article, but how do we connect them? We have to write some CSS glue for the layout itself.
Another problem arises when we have some nested partials. The inner partial's HTML code could potentially depend on the outer context for layout. If we had a helper partial `H`, partials `A` and `B` which depended on `H`, and the partial `H` depended on the outer context for styles, then we wouldn't be able to write the styles of `H` completely independently. We would somehow have to connect `A` and `B` with `H` in CSS.
## Closing thoughts
CSS styles remain an open-ended question, as there's not a single ideal solution to writing styles. We can only try to always write the most manageable and scalable stylesheets possible. It's also entirely up to us to figure out how to do it, because different approaches might yield different results in different projects.