Rediscovering Modern CSS
The problems we used to solve with JavaScript and preprocessors now have native CSS answers: container queries, :has(), and native nesting.
I learned CSS years ago and for a long time I assumed I already knew it. We built layouts with floats, vertical centering was a little puzzle, and whenever we needed to style a component based on some measured condition we reached for JavaScript. Over the past few years, CSS quietly transformed into a different language — and I was slow to notice, because I kept writing it the old way. This post covers the three modern CSS additions that have saved me the most time.
Container queries: the component decides its own context
We spent years writing media queries: “if the screen is this wide, do this.” But how a component should look usually depends not on the screen width, but on the width of its containing area. The same card component might live inside a wide content column and a narrow sidebar at the same time.
Container queries solve exactly this:
.kart-listesi {
container-type: inline-size;
}
@container (min-width: 400px) {
.kart { grid-template-columns: auto 1fr; }
}
Now .kart lays itself out based on the width of .kart-listesi, not the viewport. This removes a long-standing barrier to writing truly reusable components.
:has(): CSS finally has a parent selector
For years, the most-wanted CSS feature was “select an element based on its contents.” Saying “align cards that contain an image differently” meant JavaScript or an extra class.
.kart:has(img) { grid-template-rows: 200px 1fr; }
.form-alani:has(input:invalid) { border-color: crimson; }
:has() is more than just a parent selector — it lets you select an element based on any descendant state. A lot of things I used to handle with a small JavaScript listener are now a single line of CSS.
Native nesting: one less reason to reach for a preprocessor
I used Sass for years almost exclusively for two things: variables and nesting. CSS custom properties have long taken over the variables job; nesting is now built into CSS itself:
.kart {
padding: 1rem;
& .baslik { font-weight: 600; }
&:hover { box-shadow: 0 4px 12px rgb(0 0 0 / 0.1); }
}
On smaller projects, this can eliminate a build step and a dependency entirely. Every dependency is a maintenance burden; if the language itself does the job, there is no reason to carry the extra layer.
Adopting these features incrementally
There is a balance I keep in mind when using all three of these features: browser support. :has(), container queries, and native nesting all enjoy wide support now, but every project has a different audience. So I tend to use them through a progressive enhancement lens — the baseline experience holds up without them, and they simply add a layer of improvement on top.
A concrete example: in an older browser that does not support container queries, the card component falls back to a reasonable single-column layout — not perfect, but entirely usable. The user never perceives anything as broken; they just see a slightly simpler layout. Adopting a new CSS feature does not mean sprinkling it everywhere; it means being confident it degrades gracefully when unsupported. That discipline lets me start using new features the day they ship, without turning adoption into an all-or-nothing decision.
The real lesson
What these three additions have in common is this: problems we spent years solving with JavaScript, preprocessors, or extra markup are now solved by the platform itself. The deeper lesson here is not technical: it is about relearning a tool you thought you already knew. It is easy to learn a technology once, put it on the shelf, and say “I know that one” — but even mature tools evolve quietly. I have made it a habit to read the release notes of a tool I think I know well at least once a year. More often than not, that is where I discover that something I had been doing the hard way for years now takes a single line. Experience has a trap: it convinces you the world froze at the moment you learned it.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.