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
Submitting this form will not sign you up for any marketing lists. Your information is strictly used and stored for contacting you. Privacy Policy
Related posts: