Working with CSS a decade later
Introduction
It’s a common trope that those who handle their own technical blog always end up writing an inordinate amount about the tech stack behind it. It’s a trope that is also very true. Maintaining your own static website for example requires a lot more work than you’d expect, so really passionate people tend to gravitate towards it. That can lead to fantastic articles. And with this article, I’m now part of the club!
This blog is mostly just a simple Astro website hosted on a Hetzner VPS. Nothing fancy. Frontend-wise, I initially repurposed this great theme, which came with Tailwind. When I started working on this base, I hadn’t touched a frontend project since 2015. Things looked a lot different back then. I remember Bootstrap (which is still here but looks a lot different now), the early days of Node.js as a backend, but mostly I remember that CSS was a huge pain. Let alone the ridiculous amount of browser idiosyncrasies I needed to remember, the language itself was rather tedious for anything beyond basic theming.
In 2026, the landscape has changed greatly. Frontend and backend don’t feel as separated as I remember them to be thanks to React (although MVC frameworks back then already carried the philosophy behind it), Node.js is king, and Tailwind seems to have won the CSS battle.
So I figured I would get with the times and embrace this. The ‘TSX’ part (instead of HTML really) I’m okay with. It’s actually quite nice (as long as you stay away from hydration specifics). But Tailwind, I struggled with. At first glance, the utilities-based system they have going on is quite remarkable. I could build things so quickly, things a lot more complex than I remembered doing before, for a fraction of the effort. In a way, it reminds me of using LLMs. This feeling is quite intoxicating: you can iterate and refine ideas faster, writing the code itself (not just typing it but getting it right) becomes a breeze. But at the same time, things get messy fast. Utility classes everywhere means there’s now a dozen spots to edit for the smallest UX change. And this is really not made easier by CSS cascading rules.
But sometimes getting things done trumps perfection. This is a hobby website, so I didn’t feel like spending a lot of time rewriting the whole CSS architecture, especially to just achieve the same look. But eventually, I bit the bullet and decided I would give it a shot. In doing so, I discovered that vanilla CSS is a lot better than it was back then. Flexboxes, grids, variables, so many features you would have needed a postprocessor or dedicated framework before. I had read about it, but I wasn’t expecting it to be so pleasant!
Modern vanilla CSS
This website provided with a really good starting point. The structure I chose is wildly inspired by it:
index.cssstyles/├── reset.css├── variables.css├── defaults.css├── utilities.css└── components/ ├── index.css ├── button.css └── ...The root index.css is just there to import all others and declare all layers (first reset, then a base layer, then utilities and finally components):
@layer reset, base, utilities, components;
@import './styles/reset.css';@import './styles/variables.css';@import './styles/defaults.css';@import './styles/utilities.css';@import './styles/components/index.css';Note that the file styles/components/index.css follows a similar structure importing the components stylesheets. This is a really good way to keep styles organized while only having one <link> import in the source. Each layer has its own role to play of course. The reset layer contains a lot of normalizing rules to reach a cross-browser baseline. Even in 2026, when most browsers are Chromium-based, some differences in how elements are rendered remain and need to be smoothed out.
For example, box-sizing: border-box is not the default in all browsers (for compatibility reasons) which can be a pain to deal with.
The base layer contains the basic styles for all elements. Things like body font, colors, etc. It’s also where I place all the variables I use to define the styles. One of the great things about modern CSS is that you can use variables natively. In this layer, I place all the values I plan on reusing: website colors, font sizes, a scale of spacing, etc. It’s a great way to keep your design consistent.
I also use this layer to set the light and dark themes. Each variable is declared as a light-dark(..., ...) pair,1 and then I can just assign color-scheme: light or color-scheme: dark to :root to switch between them (for example using a .dark class selector).
In utilities, I keep some utility classes, mostly about layout. For example, .flex, wrap or items-center.
Finally, the components layer is where I would place styles for custom components I reuse across the website. In a way they behave a bit like utilities, e.g., a .button or .card utility class. They’re the right abstraction level for components with a specific look across the website. I started with a lot of those, but then I found out a neat feature of Astro which really made me rethink my approach.
Scoped styling in Astro
A great thing about Astro is that you can make a component in an .astro file, for example
<button style="border: 2px solid black; border-radius: 5px; padding: 3px;"><slot /></button>Once defined, you can use this component anywhere (other .astro files or even .mdx files) just by importing it and using it as a tag:
---import Button from '../components/Button.astro'---
<Button>My amazing button!</Button>and this is what it looks like:
In this example, I placed the style directly in the <button> tag, but I could have just created a .button class with all these rules. However in Astro there is a third option which is particularly cool. Every component has an attribute of the kind data-astro-cid-XXXXXX, which is unique per component. Not per instance, but component. So all the buttons of my website using that component would have it. So instead of writing a .button class, I can also just do
button[data-astro-cid-XXXXXX] { border: 2px solid black; border-radius: 5px; padding: 3px;}This is very useful because this kind of rule is very specific, and in CSS the more specific a rule is, the higher priority it has.
Of course, keeping track of all of these identifiers is not easy, especially since they can change when you edit the component. So instead, what you should do is use scoped styles without mentioning the attribute, and Astro will automatically generate CSS rules with the identifier-attribute.
<button><slot /></button>
<style>button { border: 2px solid black; border-radius: 5px; padding: 3px;}</style>Since I was already placing most of my components in individual Astro files, moving the styles there was easy, and it really helped declutter the styles/components folder.
Overall, I was pleasantly surprised by how far vanilla CSS has come since I last built a project from scratch. And with Astro’s scoped styles, component-specific styles are a breeze. My days of frontend work are behind me, but at least now if I have to design a mock frontend at work (which happens more often than I’d like), I know it won’t be a nightmare!
Footnotes
-
This a recent addition to CSS which simplifies greatly the handling of light and dark theme variables. Instead of switching the value of a variable in multiple places (one for light and one for dark mode contexts), you declare it once (with both values), and only switch the
color-schemeproperty fromlighttodarkwhen convenient. ↩