New CSS that can actually be used in 2024#
The amount of CSS novelty in the last two to four years has been staggering. Multiple innovations have been released and are now supported in all modern browsers, and some of them fundamentally change how to make websites.
This page is a cheat sheet for new CSS things I want to start using in 2024. Note that, to support older browsers, it's sometimes a necessity to use polyfills.
If you allow me a little rant
The amount of stuff our influencers hype on their websites and newsletters needs to slow down. I know they sincerely like CSS, that their professional life depends on staying up to date and looking relevant in CSS, but damn. When I want to update myself, I have to spend hours checking on Can I Use if the things they talked about in a blog post in 2021 actually exist in my browser ; only to find half of it stuck in technical preview purgatory. It makes me resentful towards people and I don't like that. See also: The continuing tragedy of CSS.Logical properties#
For the longest time many css properties used physical directions to set values. For example : margin-top
, padding-right
, bottom
, border-left
. But those properties don't take into account switching to another reading direction, that can be set using direction, text orientation, and writing mode.
For example, a border placed on the left of a text would stay on the left if the text reading direction was reverted, instead of moving to the right to have a similar user experience. Logical properties avoid this issue by allowing setting properties on the X (called inline) and Y (called _block) axis.
I won't go into the whole spec (see MDN) but here are the most common use cases.
For height
and width
and their variants like max-width
and min-width
:
height
:block-size
width
:inline-size
max-width
:max-inline-size
min-height
:min-block-size
For margin
, padding
, border
, and variants:
left
andright
:inline
left
:inline-start
right
:inline-end
top
andbottom
:block
top
:block-start
bottom
:block-end
For inset properties like top
, bottom
, left
, right
:
left
andright
:inset-inline
left
:inset-inline-start
right
:inset-inline-end
top
andbottom
:inset-block
top
:inset-block-start
bottom
:inset-block-end
Detail of browsers versions supporting this feature: Can I Use.
Container queries#
Media queries allowed to do an if/else
using the browser width or height as a condition. Container size queries allow the same if/else
logic, but for any container, not just the browser. You declare a piece of the dom as the container:
.card {
container-type: size|inline-size|normal;
}
There are three values possible:
size
- Allows to use the inline and block dimensions, as well as style queries.
inline-size
- Same as above but only for
inline
dimension (this is the most useful for responsive design). normal
- Deactivate size queries, only let style queries.
And then use it as a media query:
@container (min-width: 700px) {
.card h2 {
font-size: 2em;
}
}
It's also possible to name containers, using the container-name
property, or a shortcut width container
.
.card {
container: sidebar / inline-size;
}
@container sidebar (min-width: 700px) {
.card {
font-size: 2em;
}
}
Container size queries also have their own units:
cqw
- 1% of a query container's width
cqh
- 1% of a query container's height
cqi
- 1% of a query container's inline size
cqb
- 1% of a query container's block size
cqmin
- The smaller value of either cqi or cqb
cqmax
- The larger value of either cqi or cqb
Detail of browsers versions supporting this feature: Can I Use.
:has
:has permalink" style="color:currentColor;opacity:.4;font-size:.4em;margin-left:1ch;text-decoration:none" title="Link to this title">#
To understand :has
it's important to remember that CSS styling works has a cascade that goes from left to right (even if the browser reads it right to left but that's another topic). The last element on a declaration is the one being styled.
For example, take this HTML where inside main
, we select all the article
elements, then the strong
elements.
<main>
<article>
<p>Some text</p>
<strong>My strong text</strong>
<p>Some text</p>
</article>
</main>
main article strong {
font-weight: 600;
}
But what happens when we want to style main
at the condition that strong
exists in the HTML? That's the purpose of :has
, which will check for us.
main:has(strong) {
//change main style
}
It's also possible to simulate logical operators likes &&
and ||
. to check multiple conditions, like if main
has p
and has strong
:
main:has(p):has(strong) {
color:red;
}
If main
has p
or has strong
:
main:has(p, strong) {
color:red;
}
But that's not all. :has
can be combined with CSS selectors like +
, ~
, >
or other pseudo-selectors like nth-child
or :not
to open new possibilities.
Do a negative check:
main:not(:has(strong)) {
//change main style
}
Check if strong
is after p
, if yes change previous p
color to red.
p:has(+strong) {
color:red;
}
Check if strong
is after p
, if yes change all the previous p
color to red.
p:has(~ figure) {
color:red;
}
For more interactive examples and concrete use cases, see Ahmad Shadeed's article on the topic.
Detail of browsers versions supporting this feature: Can I Use.
:is
and :where
:is and :where
permalink" style="color:currentColor;opacity:.4;font-size:.4em;margin-left:1ch;text-decoration:none" title="Link to this title">#
They have the same use: select multiple things in a more concise syntax. For example, chaining of selectors or classes in menus made of lists are a classic issue of specific styling that creates a lof of specificity:
ol ol ul,
ol ul ul,
ul ol ul,
ol, ol, ol,
ul ul ul,{
list-style-type: square;
}
Can be replaced by :
:is(ol, ul) :is(ol, ul) ul {
// My style
}
// or
:where(ol, ul) :where(ol, ul) :where(ol, ul) {
// My style
}
There are more good examples on the MDN page.
The difference between :is
and :where
is a CSS nerd thing who might, in the long run, make non-CSS specialist throw their keyboard in frustration: is:
takes the specificity of the most specific thing it contains, while :where
does not.
In a more concrete way, it means something styled inside an :is
, if it has a bigger specificity like being a class, an id, or using !important
, cannot be changed by a lower specificity rule even if this rule comes down after in the cascade.
For example this HTML:
<main>
<p class="isClass">Text 1</p>
<p class="whereClass">Text 2</p>
</main
With this CSS:
:is(.isClass) {
color:red;
}
:where(.whereClass) {
color:blue;
}
Looks the same. But what happens if we add this CSS?
main p {
color: orange;
}
Since main p
has a lower specificity than .isClass
, it cannot restyle it. Meanwhile, .whereClass
has a specificity of 0
, and will become orange due to the incoming rule in the cascade.
Detail of browsers versions supporting :is
: Can I Use.
Detail of browsers versions supporting :where
: Can I Use.
Nesting#
It's quite obvious, it's the nesting from SASS, but without string concatenation (no gluing .myclass
with a __children
like we do in BEM). Nesting can be used with the nesting selector &
or without it for simple use cases.
main {
article {
color:red;
}
}
// Same as :
main {
& article {
color:red;
}
}
The nesting selector is necessary for compound selectors (a combination of element + class like p.myClass
) and pseudo classes (:hover,
:focus`, etc).
` main { &.myClass { //... } &:hover { //... } }
`Detail of browsers versions supporting this feature: Can I Use.
CSS Comparison Functions#
CSS Comparison Functions allow to compare values to the viewport (or the container if using container queries) and let the browser pick the most appropriate one. Basically a if/else
that comes in three ways:
min(a, b)
- Takes the smallest value between
a
andb
max(a, b)
- Takes the biggest value between
a
andb
clamp(a, b, c)
- Takes the most appropriate value between smallest
a
, desiredb
, and biggestc
.
But comparisons can be done with different values than pixels. %
, em
, rem
, vw
vh
, among others are available. And since math expressions are baked in, it's possible to compare fixed values to live computed values based on the viewport.
For example, for a fluid font who's size will vary when between 16px and 32px, it can be done by summing the viewport width and the relative font size:
font-size: clamp(16px, 1vw + 1rem, 32px);
If you ever struggled adjusting things in media queries with a designer behind you, you understood immediately how revolutionary this is. In a world where designers only produce the mobile and desktop versions of their designs (and sometimes the tablet one), it allows to proportionally automate all the in-between for things like font sizes, margins, spacings, grid and flex gaps.
I have the feeling that this technique hasn't yet got out of the CSS specialist crowd despite being several years old. It may be due to its complexity, the use of frameworks and lack of knowledge in CSS.
If you are interested, visit utopia, which contains easy to use tools to generate the comparison functions for you. You can directly enter the values given by a designer and get the clamp()
code as custom properties (aka CSS variables) to maintain your website cohesive.
Detail of browsers versions supporting this feature: Can I Use.
Cascade layers#
Cascade layers allow the creation of multiple CSS cascades and their ordering. The cascade is often the most dreaded aspect of CSS and has led to the creation of multiple methodologies to keep code ordered.
Cascade layers, like the name implies, offers to create separated cascades you can access anywhere in the files, and to layer them in the order we want.
@layer reset base; // Define the order of layers
// Put some code inside the reset layer
@layer reset {
a {
color: black;
}
}
// Put some code inside the base layer
@layer base {
a {
color: grey;
}
}
// Put more code inside the reset layer
@layer reset {
p {
color: red;
}
}
But it doesn't mean that each layer is scoped (@scope exists but is not widely supported yet. In the example above, the color of a
will be grey, as the base
layer is after the reset
layer. This is an organization tool, not a scoping mechanism.
This is especially useful in large code bases. It allows to force the code into a specific order, reduces the stress of looking where to add new code, and if it will affect the rest of the code.
For example, let's say we want to import a third party CSS stylesheet, add it to a layer, and build our own styles on top of it.
// import framework.css and define as framework layer
@import "framework.css" layer(framework);
// Order framework before personal
@layer framework personal;
@layer personal {
// My own styles
}
But later in the project, we notice the framework has a buggy style. We can fix it in the framework
layer without touching the personal
layer. And even better, we don't have to go back at the start of the file:
@import "framework.css" layer(framework);
@layer framework personal;
@layer personal {
// My own styles
}
@layer framework {
// Small bug fix
}
Last thing: un-layered style always has the priority on layered styles. In the examples above, a style written outside a layer would have erased the previously defined ones. See this very extensive guide to better understand the layering of styles in browsers.
Detail of browsers versions supporting this feature: Can I Use.
Subgrid#
The subgrid
property has been long in the waiting and is finally here, and mostly used to fix one very annoying issue. Imagine several columns of the same height, and each of them has the same number of children, but those children don't have the same height.
When looking at the whole, the children are not vertically aligned:
Declaring the children as a grid element and subgrid
as the value of grid-template-rows
allows to align the children:
.parent {
display: grid;
gap: 1rem;
grid-template-columns: 1fr 1fr;
}
.children {
grid-row: span 2;
display: grid;
grid-template-rows: subgrid;
}
There is of course more to subgrid
than this. Ahmad Shadeed wrote an extensive article on the topic.
Detail of browsers versions supporting this feature: Can I Use.
Small but noticeable#
text-wrap:balance
- Allows multiple lines of text to have their lines broken in such a way that each line is roughly the same width. MDN - Can I Use Page
- Dynamic viewport height or
dvh
- Takes into account the url bar and UI of browser, unlike
vh
which did not. Web.dev - Can I Use Page accent-color
- Helps coloring HTML controls that are traditionally cumbersome, like checkboxes, ranges, etc. MDN - Can I Use Page
- Media Queries Range Syntax
- Allows the use of mathematical comparison operators:
>
,<
,>=
, or<=
in media queries. Ex:@media (width <= 1140px)
. Web.dev - Can I Use Page - flexbox
gap
- Same as grid, but now supported in flex. MDN - Can I Use Page
Modified and/or rebuilt: Mon, Apr 29, 2024, 4:50 PM