What You Need to Know about Modern CSS (2025 Edition)
What You Need to Know about Modern CSS (2025 Edition) êŽë š
We published an edition of What You Need To Know about Modern CSS last year (2024), and for a while I really wasnât sure if only a year later weâd have enough stuff to warrant and new yearly version. But time, and CSS, have rolled forward, and guess what? There is more this year than there was last. At least in this somewhat arbitrary list of âthings Chris thinks are valuable to know that are either pretty fresh or have enjoyed a boost in browser support.â
Animate to Auto
What is this?
We donât often set the height
of elements that contain arbitrary content. We usually let elements like that be as tall as they need to be for the content. The trouble with that is we havenât been able to animate from a fixed number (like zero) to whatever that intrinsic height is (or vice versa). In other words, animate to auto
(or other sizing keywords like min-content
and the like).
Now, we can opt-in to being able to animate to these keywords, like:
html {
interpolate-size: allow-keywords;
/* Now if we transition
"height: 0;" to "height: auto;"
anywhere, it will work */
}
If we donât want to use an opt-in like that, alternatively, we can use the calc-size()
function to make the transition work without needing interpolate-size
.
.content {
height: 3lh;
overflow: hidden;
transition: height 0.2s;
&.expanded {
height: calc-size(auto, size);
}
}
Why should I care?
This is the first time weâve ever been able to do this in CSS. Itâs a relatively common need and itâs wonderful to be able to do it so naturally, without breaking behavior.
And itâs not just height
(it could be any property that takes a size) and itâs not just auto
(it could be any sizing keyword).
Support
Browser Support
Just Chrome.
Progressive Enhancement
Yes! Typically, this kind of animation isnât a hard requirement, just a nice-to-have.
Polyfill
Not really. The old fallbacks including things like animating max-height
to a beyond-what-is-needed value, or using JavaScript to attempt to measure the size off-screen and then doing the real animation to that number. Both suck.
Usage Example
Popovers & Invokers
These are separate and independently useful things, and really rather HTML-focused, but itâs nice to show them off together as they complement each other nicely.
What is this?
A popover
is an attribute you can put on any HTML element that essentially gives it open/close functionality. It will then have JavaScript APIs for opening and closing it. Itâs similar-but-different to modals. Think of them more in the tooltip category, or something that you might want more than one of open sometimes.
Invokes are also HTML attributes that give us access to those JavaScript APIs in a declarative markup way.
Why should I care?
Implementing functionality at the HTML level is very powerful. It will work without JavaScript, be done in an accessible way, and likely get important UX features right that you might miss when implementing yourself.
Support
Browser Support
Popovers are everywhere, but invokers are Chrome only at time of publication. There are sub-features here though, like popover="hint"
which has slightly less support so far.
Progressive Enhancement
Not so much. These type of functions typically need to work, so ensuring they do with a polyfill instead of handling multiple behaviors is best. |
Polyfill
Yep! For both:
Usage Example
Remember there are JavaScript APIs for popovers also, like myPopover.showPopover()
and secondPopover.hidePopover()
but what Iâm showing off here is specifically the HTML invoker controls for them. There are also some alternative HTML controls (e.g. popovertarget="mypopover" popovertargetaction="show"
) which I suppose are fine to use as well? But something feels better to me about the more generic command invokers approach.
Also â remember popovers pair particularly well with anchor positioning which is another CSS modern miracle.
@function
What is this?
CSS has lots of functions already. Think of calc()
, attr()
, clamp()
, perhaps hundreds more. They are actually technically called CSS value functions as they always return a single value.
The magic with with @function
is that now you can write your own.
@function --titleBuilder(--name) {
result: var(--name) " is cool.";
}
Why should I care?
Abstracting logic into functions is a computer programming concept as old as computers itself. It can just feel right, not to mention be DRY, to put code and logic into a single shared place rather than repeat yourself or complicate the more declarative areas of your CSS with complex statements.
Support
Browser Support
Chrome only
Progressive Enhancement
It depends on what youâre trying to use the value for. If itâs reasonable, it may be as simple as:
property: fallback;
property: --function();
Polyfill
Not really. Sass has functions but are not based on the same spec and will not work the same.
Usage Example
Other Resources
- Juan Diego RodrĂguez: Functions in CSS?!
if()
What is this?
Conceptually, CSS is already full of conditional logic. Selectors themselves will match and apply styles if they match an HTML element. Or media queries will apply if their conditions are met.
But the if()
function, surprisingly, is the first specific logical construct that exists soley for the function of applying logical branches.
Why should I care?
Like all functions, including custom @functions like above, if()
returns a single value. It just has a syntax that might help make for more readable code and potentially prevent certain types of code repetition.
Support
Browser Support
Chrome only
Progressive Enhancement
It depends on the property/value you are using it with. If youâre OK with a fallback value, it might be fine to use.
property: fallback;
property: if(
style(--x: true): value;
else: fallback;
);
Polyfill
Not really. CSS processes tend to have logical constructs like this, but they will not re-evaluate based on dynamic values and DOM placement and such.
Usage Example
Baking logic into a single value like this is pretty neat!
.grid {
display: grid;
grid-template-columns:
if(
media(max-width > 300px): repeat(2, 1fr);
media(max-width > 600px): repeat(3, 1fr);
media(max-width > 900px): repeat(auto-fit, minmax(250px, 1fr));
else: 1fr;
);
}
The syntax is a lot like a switch statement with as many conditions as you need. The first match wins.
if(
condition: value;
condition: value;
else: value;
)
Conditions can be:
media()
supports()
style()
field-sizing
What is this?
The new field-sizing
property in CSS is for creating form fields (or any editable element) that automatically grows to to the size of their contents.
Why should I care?
This is a need that developers have been creating in JavaScript since forever. The most classic example is the <textarea>
, which makes a lot of sense to be sized to as large as the user entering information into it needs to be, without having to explicitly resize it (which is difficult at best on a small mobile screen). But inline resizing can be nice too.
Support
Browser Support
Chrome and looks to be coming soon to Safari.
Progressive Enhancement
Yes! This isnât a hard requirement usually but more of a UX nicety.
Polyfill
There is some very lightweight JavaScript to replicate this if you want to.
Usage Example
Custom Selects
What is this?
Styling the outside of a <select>
has been decently possible for a while, but when you open it up, what the browser renders is an operating-system specific default. Now you can opt-in to entirely styleable select menus.
Why should I care?
Support
Browser Support
Chrome only
Progressive Enhancement
100%. It just falls back to a not-styled <select>
which is fine.
Polyfill
Back when this endeavor was using <selectlist>
there was (luwes/selectlist-polyfill
), but in my opinion the progressive enhancement story is so good you donât need it.
Usage Example
First you opt-in then you go nuts.
select,
::picker(select) {
appearance: base-select;
}
text-wrap
What is this?
The text-wrap
property in CSS allows you to instruct the browser that it can and should wrap text a bit differently. For example, text-wrap: balance;
will attempt to have each line of text as close to the same length as possible.
Why should I care?
This can be a much nicer default for large font-size
elements like headers. It also can help with single-word-on-the-next-line orphans, but there is also text-wrap: pretty;
which can do that, and is designed for smaller-longer text as well, creating better-reading text. Essentially: better typography for free.
Support
Browser Support
balance
is supported across the board but pretty
is only Chrome and Safari so far.
Progressive Enhancement
Absolutely. As important as we might agree typography is, without these enhancements the text is still readable and accessible.
Polyfill
Usage Example
Resources
linear()
easing
What is this?
I think this one a little confusing because linear
as a keyword for transition-timing-function
or animation-timing-function
kinda means âflat and boringâ (which is sometimes what you want, like when changing opacity for istance). But this linear()
function actually means youâre about to do an easing approach that is probably extra fancy, like having a âbouncingâ effect.
Why should I care?
Even the fancy cubic-bezier()
function can only do a really limited bouncing affect with an animation timing, but the sky is the limit with linear()
because it takes an unlimited number of points.
Support
Browser Support
Across the board |
Progressive Enhancement
Sure! You could fall back to a named easing value or a cubic-bezier()
Polyfill
Not that I know of, but if fancy easing is very important to you, JavaScript libraries like GSAP have this covered in a way that will work in all browsers.
Usage Example
.bounce {
animation-timing-function: linear(
0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141 13.6%, 0.25, 0.391, 0.563, 0.765,
1, 0.891 40.9%, 0.848, 0.813, 0.785, 0.766, 0.754, 0.75, 0.754, 0.766, 0.785,
0.813, 0.848, 0.891 68.2%, 1 72.7%, 0.973, 0.953, 0.941, 0.938, 0.941, 0.953,
0.973, 1, 0.988, 0.984, 0.988, 1
);
}
shape()
What is this?
While CSS has had a path()
function for a while, it only took a 1-for-1 copy of the d
attribute from SVGâs <path>
element, which was forced to work only in pixels and has a somewhat obtuse syntax. The shape()
function is basically that, but fixed up properly for CSS.
Why should I care?
The shape()
function can essentially draw anything. You can apply it as a value to clip-path
, cutting elements into any shape, and do so responsively and with all the power of CSS (meaning all the units, custom properties, media queries, etc). You can also apply it to offset-path()
meaning placement and animation along any drawable path. And presumably soon shape-outside
as well.
Support
Browser Support
Itâs in Chrome and Safari and flagged in Firefox, so everywhere fairly soon.
Progressive Enhancement
Probably! Cutting stuff out and moving stuff along paths is usually the stuff of aesthetics and fun and falling back to less fancy options is acceptable.
Polyfill
Not really. Youâre better off working on a good fallback.
Usage Example
Literally any SVG path can be converted to shape()
.
.arrow {
clip-path: shape(
evenodd from 97.788201% 41.50201%,
line by -30.839077% -41.50201%,
curve by -10.419412% 0% with -2.841275% -3.823154% / -7.578137% -3.823154%,
smooth by 0% 14.020119% with -2.841275% 10.196965%,
line by 18.207445% 24.648236%, hline by -67.368705%,
curve by -7.368452% 9.914818% with -4.103596% 0% / -7.368452% 4.393114%,
smooth by 7.368452% 9.914818% with 3.264856% 9.914818%,
hline by 67.368705%, line by -18.211656% 24.50518%,
curve by 0% 14.020119% with -2.841275% 3.823154% / -2.841275% 10.196965%,
curve by 5.26318% 2.976712% with 1.472006% 1.980697% / 3.367593% 2.976712%,
smooth by 5.26318% -2.976712% with 3.791174% -0.990377%, line by 30.735919% -41.357537%,
curve by 2.21222% -7.082013% with 1.369269% -1.842456% / 2.21222% -4.393114%,
smooth by -2.21222% -7.082013% with -0.736024% -5.239556%,
close
);
}
The natural re-sizeability and more readable syntax is big advantage over path()
:
More Powerful attr()
What is this?
The attr()
function in CSS can pull the string value of the matching HTML element. So with <div data-name="Chris">
I can do div::before { content: attr(data-name); }
to pull off an use âChrisâ as a string. But now, you can apply types to the values you pull, making it a lot more useful.
Why should I care?
Things like numbers and colors are a lot more useful to pluck off and use from HTML attributes than strings are.
attr(data-count type(<number>))
Support
Browser Support
Chrome only
Progressive Enhancement
It depends on what youâre doing with the values. If youâre passing through a color for a little aesthetic flourish, sure, it can be a enhancement that fallback to something else or nothing. If itâs crucial layout information, probably not.
Polyfill
Not that I know of.
Usage Example
Reading Flow
What is this?
There are various ways to change the layout such that the visual order no longer matches the source order. The new reading-order
property allow us to continue to do that while updating the behavior such that tabbing through the elements happens in a predictable manner.
Why should I care?
For a long time weâve been told: donât re-order layout! The source order should match the visual order as closely as possible, so that tabbing focus through a page happens in a sensible order. When you mess with the visual order and not source order, tabbing can become zig-zaggy and unpredictable, even causing scrolling, which is a bad experience and a hit to accessibility. Now we can inform the browser that weâve made changes and to follow a tabbing order that makes sense for the layout style weâre using.
Support
Browser Support
Chrome only
Progressive Enhancement
Not particularly. We should probably not be re-ordering layout wildly until this feature is more safely across all browsers.
Polyfill
No, but if you were so-inclined you could (hopefully very intelligently) update the tabindex
property of the elements to a sensible order.
Usage Example
.grid {
reading-flow: grid-rows;
}
Re-ordering a grid layout is perhaps of the most common things to re-order, and having the tabbing order follow the rows after re-arranging is sensible, so thatâs what the above line of code is doing. But youâll need to set the value to match what you are doing. For instance if you are using flexbox layout, youâd likely set the value to flex-flow
. See MDN for the list of values.
Resources

- Daniel Schwarz: What We Know (So Far) About CSS Reading Order
Stuff to Keep an Eye On
- âMasonryâ layout, despite having different preliminary implementations, is not yet finalized, but there is enough movement on it it feels like weâll see that get sorted out next year. The most interesting development at the moment is the proposal of
item-flow
and how that could not only help with Masonry but bring other layout possibilities to other layout mechanisms beyond grid. - The CSS function
random()
is in Safari and itâs amazing. - The CSS property
margin-trim
is super useful and weâre waiting patiently to be able to use it more than just Safari. - The
sibling-index()
andsibling-count()
functions are in Chrome and, for one thing, are really useful for staggered animations. - For View Transitions,
view-transition-name: match-element;
is awfully handy as it prevents us from needing to generate unique names on absolutely everything. Also â Firefox has View Transitions in development, so thatâs huge. - We should be able to use
calc()
to multiply and divide with units (instead of requiring the 2nd to be unitless) soon, instead of needing a hack (janeori
). - We never did get âCSS4 (
w3c/csswg-drafts
)â (Zoran explains nicely) but I for one still think some kind of named versioning system would be of benefit to everyone. - If youâre interested in a more straightforward list of ânew CSS thingsâ for say the last ~5 years, Adam Argyle has a great list.
Great Stuff to Remember
- Container queries (and units) are still relatively new and the best thing since media queries in CSS.
- The **
:has()
pseudo-class** is wildly useful for selecting elements where the children exist or are in a particular state. - Ultra cool modern CSS features like View Transitions, Anchor Positioning, and Scroll-Driven Animations have all made it to Safari.
- All the useful extra viewport units (shoutout
dvh
) are now in baseline.