Modern CSS: a complete overview
What modern CSS can do: sixteen features with their support, what used to need JavaScript or Sass, pros and cons, four examples checked in a browser, mistakes and rules.
In short
Modern CSS is the set of features that appeared in browsers over the last few years and changed how sites are built: container queries, the :has() selector, subgrid, nesting, cascade layers, new colour functions and fluid sizes. Together they remove most of the JavaScript that used to be written for layout and states, and replace Sass for everyday work. A card adapts to the column it is placed in, a form reacts to input without scripts, cards in a row align by themselves. The main features already work in all modern browsers; the newest ones are added carefully, with a fallback.
Modern CSS at a glance
The main facts in one table — what is meant, what it replaces and how to check support.
- What it is
- CSS features that reached all major browsers in recent years
- Main features
- Container queries, :has(), subgrid, nesting, @layer, oklch(), clamp()
- Replaces
- Layout scripts, part of the interface libraries, Sass for everyday work
- Build
- Not needed — the browser reads it as is
- Support
- Checked by the Baseline label on MDN and caniuse
- Fallback
@supports— new features only where the browser understands them- Standard
- W3C CSS Working Group; browsers agree on priorities in the yearly Interop project
What modern CSS can do: 16 features
Each feature with the task it solves and its support. “All modern browsers” means current Chrome, Safari, Firefox and Edge; for the newest features check support before use.
| Feature | What it solves | Support |
|---|---|---|
| Grid | two-dimensional layout of a page or a block | everywhere |
| subgrid | aligning the insides of neighbouring cards | all modern |
| @container | a component adapts to its place, not to the screen | all modern |
| :has() | styling an element by what is inside it | all modern |
| nesting | rules inside rules, as in Sass | all modern |
| @layer | the order of the cascade without !important | all modern |
| @property | typed variables that can be animated | all modern |
| oklch(), color-mix() | even colours and shades without a preprocessor | all modern |
| clamp() | fluid sizes of text and spacing | everywhere |
| dvh, svh | the real screen height on phones | all modern |
| text-wrap: balance | even lines in headings | all modern |
| light-dark() | light and dark theme in one line | all modern |
| @starting-style | animation of appearing without scripts | all modern |
| @scope | styles limited to one part of the page | new — check |
| anchor() | tooltips and menus tied to an element | new — check |
| animation-timeline | animation driven by scrolling | new — check |
What used to need JavaScript or Sass
Ten everyday tasks: how they were solved before and what does it now.
| Task | Before | Now |
|---|---|---|
| A card for a wide and a narrow column | a script measuring the width | @container |
| Highlight a field with an error | a script adding a class | :has(:user-invalid) |
| Equal-height parts of cards | a script or fixed heights | subgrid |
| Lock scrolling under a window | a class on body from a script | html:has(dialog[open]) |
| Nested rules and variables | Sass | nesting and custom properties |
| Hover shade of a colour | Sass darken() | color-mix() |
| Text size between phone and desktop | several media queries | clamp() |
| Full-height first screen on a phone | a script reading the window height | 100dvh |
| A tooltip next to a button | a positioning library | anchor positioning |
| Smooth appearing of a window | an animation library | @starting-style |
Pros and cons of modern CSS
Modern CSS moves work from scripts to the browser. The gains and the costs both come from that.
Pros · 6
-
Less JavaScript
Layout and states no longer wait for scripts to load and run.
-
Faster pages
Fewer libraries and no layout shifts from scripts help Core Web Vitals.
-
Components that adapt themselves
One card works in the main column, the sidebar and a modal.
-
No build step
Nesting and variables no longer need a preprocessor.
-
Order in the cascade
Layers settle conflicts without !important and specificity tricks.
-
Better accessibility
States are tied to real HTML: a native dialog, form validation, focus.
Cons · 4
-
Old browsers
Outdated phones and some in-app browsers need a fallback.
-
Uneven support of the newest
Anchor positioning and scroll animations are checked for each project.
-
Knowledge goes out of date
Many developers still solve with scripts what CSS already does.
-
Silent failures
A rule the browser does not understand is skipped without an error.
What modern CSS looks like: 4 examples
Four tasks that used to need scripts. Each was checked in a real browser with real input from the keyboard.
Container queries
The same card is two columns in a 640px column and one in a 300px sidebar — on any screen width.
/* Markup: <section class="cards"> <article class="card"> <img> <div>…</div> </article> </section> */
/* The section becomes a container: the cards inside measure its width */
.cards {
container-type: inline-size;
}
.card {
display: grid;
gap: 1rem;
}
/* The card adapts to the place it is put in, not to the screen:
in a wide column — picture on the left, in a narrow sidebar — on top */
@container (width > 28rem) {
.card {
grid-template-columns: 10rem 1fr;
align-items: start;
}
}
The :has() selector
The field turns red only after the person typed and left it, not on an empty form.
/* The field reacts to what was typed into it — without JavaScript */
.field:has(input:user-invalid) {
--field-color: oklch(55% 0.2 25);
}
.field label,
.field .hint {
color: var(--field-color, inherit);
}
.field input {
border: 1px solid var(--field-color, #cfd1cf);
}
/* A card with a picture gets two columns, a card without one stays single */
.card:has(> img) {
grid-template-columns: 8rem 1fr;
}
/* While a dialog is open, the page underneath does not scroll */
html:has(dialog[open]) {
overflow: hidden;
}
Subgrid
Three cards with texts of different length: the buttons stand on one line.
/* Cards in a row: titles, texts and buttons line up across cards,
however long the text in each one is */
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
gap: 1.5rem;
}
.card {
display: grid;
grid-row: span 3; /* title, text, button */
grid-template-rows: subgrid; /* rows are shared with the neighbours */
gap: 0.75rem;
}
Layers, tokens and nesting
.hidden from the last layer beats even an id selector from an earlier one; the text is 16px at 375px and 20px at 1440px.
/* The order of layers is declared once: a later layer wins
regardless of selector weight — no !important needed */
@layer reset, tokens, components, utilities;
@layer tokens {
:root {
--ink: oklch(21% 0.006 150);
/* fluid size: 16px on a phone, 20px on a wide screen, no media queries */
--text-base: clamp(1rem, 0.9rem + 0.4vw, 1.25rem);
}
}
@layer components {
.button {
min-height: 2.75rem;
padding-inline: 1.25rem;
border: 0;
font-size: var(--text-base);
background: var(--ink);
color: white;
transition: background-color 0.15s;
/* nesting without Sass */
&:hover {
background: color-mix(in oklab, var(--ink) 80%, white);
}
&:focus-visible {
outline: 2px solid var(--ink);
outline-offset: 2px;
}
}
}
@layer utilities {
.hidden {
display: none;
}
}
Common mistakes with modern CSS
-
A query without a container
Without
container-typeon a parent,@containersilently does nothing. -
sizeinstead ofinline-sizeA container of type size loses its height from content and collapses.
-
100vh on phones
The browser bar covers the bottom of the first screen;
100dvhtakes it into account. -
:invalid instead of :user-invalid
An empty form is all red before the person has typed a single letter.
-
New features without a fallback
Where the browser does not support a feature, the layout falls apart instead of becoming simpler.
-
Layers announced in pieces
The order of layers is set by the first mention; declared in different files, it becomes random.
7 rules for modern CSS in a real project
-
01
Check Baseline
Before using a feature, look at its label on MDN: widely available, newly available or limited.
-
02
New on top of working
The basic layout works everywhere, and
@supportsadds the improvement where it is understood. -
03
Tokens in variables
Colours, spacing and sizes are custom properties in one place.
-
04
Layers at the top
One line
@layer reset, tokens, components, utilitiesat the start of the main file. -
05
Containers for components
Media queries for the page, container queries for what lives inside it.
-
06
Logical properties
padding-inlineandmargin-blockinstead of left and right — ready for any writing direction. -
07
Respect reduced motion
Every animation has a
prefers-reduced-motionbranch.
Questions about modern CSS
What is modern CSS?
The features of recent years — container queries, :has(), subgrid, nesting, layers — that replace scripts and preprocessors.
Do I still need Sass?
For a new project usually not: nesting, variables and colour functions are in CSS itself.
How do container queries differ from media queries?
Media queries look at the screen, container queries at the block the component is placed in.
Is :has() slow?
No, in ordinary use; very broad selectors such as *:has(…) on a huge page are the exception.
What about older browsers?
They get a simpler but working layout; improvements are added through @supports.
Does modern CSS help SEO?
Indirectly: less JavaScript and fewer layout shifts improve Core Web Vitals.
Can I animate page transitions with CSS?
Yes, with the View Transitions API — one line of CSS for transitions between pages of a site.
Online form
Layout
without excess
I build on modern CSS and a token system: adaptive layout without libraries, states without scripts, fast pages. Tell me about the project — I answer within one working day.