Blog

New CSS features in 2023

Edit this Post Opens in new tab

CSS made a lot of progress in 2022. Many new features were added, and existing ones were improved and made more usable. Working with CSS these days is exciting, and I have found my love for it again.

To keep an overview of what's new and what has changed, I will explain in detail in this blog post all you should know about the new CSS features in 2023.

Disclaimer: the features I talk about in this blog post are not new in CSS in 2023; they will gain major browser support soon or already have it recently. This means they are usable without thinking about polyfills, and that's the real deal.


Table of Contents:


Container Queries

Container Queries allow us to modify an element and its behavior not based on the viewport size but on the container it's within.

To use Container queries, you first have to define the container itself.

main {
  container-type: inline-size;
}

Optionally you can also set the container-type and define a container name all at once.

main {
  container: main / inline-size;
}

Next up, you have to write your container queries. This is possible by using several math operators. Using math operators in queries is also a great new addition!

The following query targets every paragraph inside our container with the name of main when the width of the container is greater or equal to 600px;

@container main (width >= 600px) {
  p {
    padding: 24px;
  }
}

Here's another example on CodePen. If the container on the right is larger than 1200px, we make a grid out of the image and the text and position them next to each other.

CodePen - Container Queries Opens in new tab

Why do we need container queries when most of the layout changes can be done by listening to the viewport width? The answer is context. Container Queries are such a perfect tool for components from whom we don't know beforehand in which context they will be used.

So we can define the appearance of our component based on the container width, and it adapts. No matter if this component is used in a header, a sidebar, or a modal, for example.

Another addition to container queries is container query units. Container queries allow us to quickly implement responsive typography using container query units.

I've already written about responsive typography using clamp().

This is typically done by listening to viewport widths. However, with the addition of container query units, we can replace this now by listening to the container's width, height, inline size, block size, or the smaller or larger value of these.

Here's the spec for all the new units: https://drafts.csswg.org/css-contain-3/#container-lengths Opens in new tab. It has yet to get full browser support, but container queries can be used in totally new ways if this lands.

Focus-visible

If you've been a web developer for a few years, chances are that your Project Manager or your Customers asked you to remove the "ugly" outlines around buttons when focused.

The following CSS achieves it:

button:focus {
  outline: none;
}

Hint: Never do that!  They are there for a reason Opens in new tab.

If you ever get asked that again, tell them there is now a better solution :focus-visible. This new pseudo-class tells the browser when to show focus rings based on heuristics.

This means it detects input modalities that require visible focus. So, for example, mouse users won't see them on click, but keyboard users will have them on tab.

This is an excellent addition to CSS, as it removes them for mouse users, who won't need it necessarily but keeps it for keyboard users, who heavily rely on it for accessibility reasons.

Try it out yourself:

CodePen - Focus-visible Opens in new tab

:has()

:has() is a new selector that, as the name says, gets applied styles if the argument passed in matches at least one element. There are endless possibilities for how to use this new selector. It opens up a lot of new ways to select an element and apply styles to it conditionally.

:has() is one of the four functional pseudo-classes along with :is(), :where(), and :not(), all of them can accept a selector list.

It's best explained by showcasing a lot of examples, so here we go:

/* selects the ul if it has a direct following li element */
ul:has(> li) {
  margin-left: 1rem;
}

/* selects the a element if it has a direct following img element */
a:has(> img) {
  border: 1px solid yellow;
}

/* selects the last li element if the ul element has li elements */
ul:has(li) li:last-child {
  margin-bottom: 0;
}

:has() can also be combined with the other pseudo-classes. And it enables us to do logical operators like AND and OR.

/*
  Selects the ul if it has a li element which has a direct a as a child
  AND if it has a li which has a span as a direct child
*/
ul:has(li > a):has(li > span) {
  color: lightblue;
}

/*
  Selects the ul if it has a li element which has a direct a as a child
  OR if it has a li which has a span as a direct child
*/
ul:has(li > a, li > span) {
  color: lightblue;
}

/* Selects the section if it hasn't a direct child of h1, which has an h2 and a p as adjacent siblings */
section:not(:has(> h1 + h2 + p)) {
  font-family: Times-New-Roman;
}

Yes, you could say that :has() is a parent selector. You can match an ancestor element based on containing a specific descendant. But that's not all!

You can also target ascendant elements if the condition returns true on the initial selector. I've already shown that in the first example, but here are some more:

/*
  Selects the p if the main has an h1 as a direct child, which is followed by an h2
  AND if the main has an h2 as a direct child who is followed by an h3
  AND if the main has an h3 as a direct child, which is followed by an h4
*/
main:has(> h1 + h2, > h2 + h3, > h3 + h4) p {
  font-size: 2rem;
}

/* Selects the label inside a p element if the p contains a form control
that is required but not currently valid. */
p:has(:required:invalid) label {
  color: red;
}

/* Selects the label inside any element if the element contains a form control
that is required but not currently valid. */
* :has(> label + :required:invalid) > label {
  color: red;
}

The examples above are from the fantastic blog post by Estelle Weyl Opens in new tab and perfectly showcase the usefulness of the :has() selector.

This means :has() is not solemnly a parent selector. It's a relational selector! As I said at the beginning, this new functional pseudo-class brings endless possibilities. It's awesome. I'm under the impression that I haven't even started to grasp the scope of what's possible now.

Subgrid

Before subgrid existed, a grid inside another grid couldn't be aligned along the tracks of the parent grid. Here's a CodePen with a grid inside another grid:

CodePen - Nested Grid without subgrid Opens in new tab

Now with subgrid, you can tell the inner grid with grid-template-columns: subgrid that the columns should be aligned with the ones of the parent.

CodePen - Nested Grid using subgrid Opens in new tab

Subgrids can be used without limitation downwards, so you can nest them endlessly as long as the parent has the grid property. This opens up many possibilities for defining grids; it's a beautiful addition!

Accent-Color

Styling form elements or adding the most minimalistic CSS changes were always a pain. You likely needed external libraries or packages to achieve that.

This changed recently with the introduction of accent-color Opens in new tab.

It allows you to set the accent-color of user-interface controls.

It's currently usable on input type= "checkbox", input type= "radio", input type="range", and <progress>.

Here's a CodePen to showcase its usage of it:

CodePen - Accent-Color Opens in new tab

Individual Transform Properties

The transform property was one of my favorite additions to CSS back when it was introduced. I love to use it to create little animations and transitions.

It lets you define one or more transform functions at once. Here's an example:

.caption:hover {
  transform: rotate(10deg), scale(1.2), translateX(-20px);
}

What happens here is that on hover, our DOM element with the class of .caption will rotate by 10 degrees clockwise, will scale up to 1.2 of its size, and will move 20px horizontally to the left.

Hi Friend!

If you enjoy my writings and learn something from them, you can consider to support me! One way of doing this is just as easy as buying me a Coffee!

Thanks so much! This means a lot to me and is a great way to support my work.

☕ Buy me a coffee ☕

There's one problem with it. If you want to change just one of the transform properties later on, like in a media or container query, you must write the whole definition again for all properties.

@media (min-width: 1200px) {
  .caption:hover {
    transform: rotate(10deg), scale(1.8), translateX(-20px);
  }
}

We've just applied one change on the scale property but had to write it for all of them again. If we wouldn't, they would reset to the default.

But this is a thing of the past now! Introducing: individual transform properties.

scale(), translate(), and rotate() can now be used individually without having to define them inside a transform declaration. Very cool!

Here's our example from above with individual transform properties:

.caption:hover {
    translate: -20px;
    scale:1.2;
    rotate: 10deg;
}

@media (min-width: 1200px) {
  .caption:hover {
     scale:1.8;
  }
}

That's it for the new CSS features you can use in 2023! A lot of cool stuff landed in CSS and it's awesome to see it being improved continuously.

Sources


I hope you enjoyed this post and learned something new. If you have any questions, feel free to reach out to me on Twitter Opens in new tab or via Email Opens in new tab.

If you want to support me, you can buy me a coffee. I would be very happy about it!

☕ Buy me a coffee ☕

I wish you a wonderful day! Marco