What You Need to Know about Modern CSS (2025 Edition)

Chris Coyier Chris Coyier on

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 */
}Code language: CSS (css)

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);
  }
}Code language: CSS (css)

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 SupportJust Chrome.
Progressive EnhancementYes! Typically, this kind of animation isn’t a hard requirement, just a nice-to-have.
PolyfillNot 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 SupportPopovers 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 EnhancementNot so much. These type of functions typically need to work, so ensuring they do with a polyfill instead of handling multiple behaviors is best.
PolyfillYep! For both:

Popovers Polyfill
Invokers Polyfill

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.";
}Code language: CSS (css)

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 SupportChrome only
Progressive EnhancementIt 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();
PolyfillNot really. Sass has functions but are not based on the same spec and will not work the same.

Usage Example

Other Resources

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 SupportChrome only
Progressive EnhancementIt 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;
);
PolyfillNot 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;
    ); 
}Code language: CSS (css)

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;
)Code language: CSS (css)

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 SupportChrome and looks to be coming soon to Safari.
Progressive EnhancementYes! This isn’t a hard requirement usually but more of a UX nicety.
PolyfillThere 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 SupportChrome only
Progressive Enhancement100%. It just falls back to a not-styled <select> which is fine.
PolyfillBack when this endeavor was using <selectlist> there was, 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;
}Code language: CSS (css)

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 Supportbalance is supported across the board but pretty is only Chrome and Safari so far.
Progressive EnhancementAbsolutely. As important as we might agree typography is, without these enhancements the text is still readable and accessible.
PolyfillThere is one for balance.

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 SupportAcross the board
Progressive EnhancementSure! You could fall back to a named easing value or a cubic-bezier()
PolyfillNot 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
  );
}
Code language: CSS (css)

Resources

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 SupportIt’s in Chrome and Safari and flagged in Firefox, so everywhere fairly soon.
Progressive EnhancementProbably! 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.
PolyfillNot 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
  );
}Code language: CSS (css)

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>))Code language: HTML, XML (xml)

Support

Browser SupportChrome only
Progressive EnhancementIt 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.
PolyfillNot 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 SupportChrome only
Progressive EnhancementNot particularly. We should probably not be re-ordering layout wildly until this feature is more safely across all browsers.
PolyfillNo, 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;
}Code language: CSS (css)

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

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() and sibling-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.
  • We never did get “CSS4” (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

Wanna learn CSS from a course?

Frontend Masters logo

FYI, we have a full CSS learning path with multiple courses depending on how you want to approach it.

7-Day Free Trial

Leave a Reply

Your email address will not be published. Required fields are marked *

$839,000

Frontend Masters donates to open source projects through thanks.dev and Open Collective, as well as donates to non-profits like The Last Mile, Annie Canons, and Vets Who Code.