Why vanilla CSS is great in 2025

5 new baseline features

By Aatu Väisänen

January 16th, 2025

Should I use Sass? Is SCSS better than CSS? Answering these questions has been easy for many web developers in the past decade: yes, of course I should choose a preprocessor like Sass or SCSS over CSS, it’s a no-brainer. Handling variables, allowing nesting, providing mixins, functions, and different mathematical operations has previously made Sass and SCSS superior to vanilla CSS. However, CSS has been constantly evolving behind the scenes over the past decade, and especially some of the latest additions to the feature list are real game-changers.

This post will cover five revolutionary CSS features, which have recently received the baseline availability status: this means that they are available to be used in the latest versions of all of the most used browsers (Chrome, Edge, Safari, and Firefox).

Better readability with CSS Nesting

One of the biggest reasons why many web developers have preferred to use Sass/SCSS over vanilla CSS has been the old, tedious syntax of writing nested style logic. Let’s say we have the following HTML structure:

Here’s the CSS needed to style these components:

.button {
padding: 8px 12px;
max-width: max-content;
border: 2px solid black;
background-color: white;
color: black;
border-radius: 4px;
font-weight: 700;
margin-bottom: 12px;
}
.content {
border: 3px solid red;
width: 300px;
height: 400px;
padding: 8px 12px;
color: red;
}
.box {
border: 3px dashed red;
width: 250px;
height: 250px;
padding: 8px 12px;
color: red;
background: #ffffff8a;
}

Let’s say we want to alter the style of the components inside the container. We want to make the button inside the container red, and the button inside the box blue. We also want to make the box inside the container have a blue color theme.

Traditionally we would have needed to specify the component hierarchy for each nested element, like this:

.content .button {
background: red;
color: white;
}
.content .box {
border-color: blue;
color: blue;
}
.content .box .button {
background: blue;
color: white;
}

It gets the job done, but with complex component hierarchies and highly customized style sheets, this syntax tends to make readability of the style sheets much more cumbersome than it needs to be.

Thanks to CSS nesting, we can simply just nest the components directly inside each other! This makes understanding the hierarchy much easier, while allowing for much more maintainable stylesheets.

.content {
.button {
background: red;
color: white;
}
.box {
border-color: blue;
color: blue;
.button {
background: blue;
color: white;
}
}
}

Personally the lack of nesting was the of the biggest reason why I have been preferring SCSS in the past, but now thanks to CSS nesting alone, vanilla CSS looks much more appealing.

Configure reusable components with @container queries

One of the most important aspects about styling web pages is its responsiveness: each component on the page should smoothly transition between different representations, depending on the size of the viewport, and size of the container it’s placed in. Previously achieving responsiveness in vanilla CSS was only possible with @media queries. This meant that the layout and style of a component could only be changed based on the entire screen size, and not on the size of its container.

What’s the problem with @media queries? It’s difficult to maintain the responsiveness of reusable components that might appear in different contexts. Lets take a look at the following example of a desktop view for a page. There is the main content area of the page on the left side, and a sidebar with suggested cards on the right. The main content contains the an instance of the same card component as the sidebar.

So, we are using the same card component in two different contexts here: the main content area, and the sidebar. Okay, what if we would want the card to behave differently inside the main content area because it looks a bit too massive? Previously achieving this would have been really difficult due to having the ability to alter the size of components only with @media queries, which again, only consider the viewport size.

With the new @container queries, we can adjust components based on the size of its container. This is really awesome, because now we can easily alter the look and feel of the card component based on how much space it has available in its specific context.

First, we’ll need to set the container-type property to the parent container element of the card. In this we’ll set it to the <main> element like this:

main {
container-type: inline-size;
}

Now we can define the @container query for the card component:

.card {
background-color: #cfdbdd;
border-radius: 8px;
padding: 16px 16px 12px 16px;
height: max-content;
display: flex;
flex-direction: column;
@container (min-width: 45ch) {
flex-direction: row;
column-gap: 16px;
width: max-content;
.title {
font-size: 28px;
}
.description {
max-width: 30ch;
}
}
}

Here’s the result.

Being able to define rules like this with @container queries makes configuring reusable components in vanilla CSS a breeze. What else is on the list?

Define custom CSS properties with @property

The @property rule is a new feature which reached the baseline availability in 2024. It allows defining and registering custom CSS properties, or commonly known as CSS variables, much more precisely than previously possible. They allow setting a type, initial value, and inheritance behavior of a property: this enables significantly better management of variables within your CSS style sheets compared to the old way of defining variables using the regular --my-variable syntax.

/* old way 🙅*/
.container {
--text-color: black;
}
/* new way with the CSS @property rule ✅*/
.container {
@property --text-color {
syntax: ‘<color’;
initial-value: black;
inherits: false;
}
}

Being able to define variables like this allows for type safety and validation, which can be essential depending on the type of project. With the @property rule, we can for example make sure that a variable named --width can only receive valid values that can represents the width of a component, like 20px or 30vw, but not #ffffff or 20deg.

The @property rule also helps tremendously with complex animations, where you need to animate properties like gradient angles or custom numerical values. This is something that was not possible to achieve before the introduction of the @property rule.

Align grid items precisely with subgrid

A common use case for the CSS grid layout is to align a list of components neatly next to each other. Let’s take the example of a pricing page for a SaaS company:

The card elements in the main grid also have the display: grid set, like so:

.grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 24px;
.card {
display: grid;
background-color: #ffffff;
box-shadow: 2px 2px 3px 0px rgba(0, 0, 0, 0.05);
border-radius: 8px;
padding: 16px 16px 12px 16px;
}
}

Everything seems to be configured with grid, so you would assume to get evenly spaced items all across the component, and that’s exactly what is happening here. However, let’s say that the text length of the title and the description changes in some of the cards.

Now the alignment of the cards has broken, because the individual grid items are not aware of changes in the layout of the other items.

The new CSS subgrid feature allows us to fix this by allowing the cards to inherit the track sizing of their parent grid, both on the columns, or on the rows.

We will first set the grid-template-rows value to the parent grid element:

.grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-rows: repeat(4, auto);
}

All we have to do in the card element is set the grid-teplate-rows property to have the value of subgrid. This means that the grid of the card follows the ruling of the parent grid, allowing for universal alignment across all the cards:

.card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 4;
}

The grid-row: span 4; rule is also important here. Basically it means that “let’s make sure that each element in the card follows the rows of the parent grid, which we defined earlier”.

The CSS subgrid feature makes it easy to implement nicely aligned layouts regardless of the content inside each individual component. I highly recommend trying it, as it will definitely enhance the experience of using CSS grid in your future web projects.

Increased performance with content-visibility

Performance is a very important metric in web development. A website with great performance can reach a larger, engaged audiences, because users are able to see, and interact with the website much quicker. Even a few second delay can increase bounce rates on your web pages. This is why performance is an important factor for Google’s site ranking algorithms.

The new CSS content-visibility property enables to instruct browsers to not render specific elements until they are needed. It is especially useful for long web pages, where a lot of content has to be loaded.

The content-visibility property can have three values: auto, hidden, and visible:

  • auto: the element will only be rendered as it enters the viewport. This can enable significant performance gains for below-the-fold content.

  • hidden: similar to setting display: none, however instead of fully hiding the element, it keeps it in the document flow. This can be useful, if the content needs to be temporarily hidden without breaking up the layout of the page.

  • visible: the content is rendered normally.

When using content-visibility: auto, it is important to note that the browser doesn’t know the exact height of the component, which can lead into issues with Cumulative Layout Shift (CLS). This can be (to some degree) prevented by specifying the contain-instrinsic-size property. For example, if content-visibility: auto is set to a card element that is known to be 300px tall, the contain-intrinsic-size should be set to have the value 0 300px.

All in all, it is good to thoroughly evaluate the use case for setting the content-visibility property, as it can introduce more problems than it solves.

Conclusion

The landscape of web development has evolved significantly, with vanilla CSS now offering powerful features that were previously only available through preprocessors like Sass and SCSS. The introduction of CSS nesting brings improved code readability and maintainability by allowing developers to write nested styles in a more intuitive way. The @container queries revolutionize responsive design by enabling components to adapt based on their container's size rather than just viewport dimensions. The @property rule enhances variable management with type safety and validation, while the subgrid feature solves complex alignment challenges in nested grid layouts. Finally, the content-visibility property offers performance optimization opportunities, though it requires careful implementation to avoid layout shifts.

While tools like Sass still have their place, vanilla CSS has matured into a more powerful and developer-friendly language. As browser support continues to improve and new features reach baseline availability, web developers can look forward to writing more maintainable, performant, and flexible style sheets using pure CSS in 2025 and beyond.

Contact us

Get in touch and let's discuss your business case

Email to sales@ikius.com or send us a message here.

Submitting this form will not sign you up for any marketing lists. Your information is strictly used and stored for contacting you. Privacy Policy