Sunday, October 19, 2025

Touring New CSS Options in Safari 26


A few days in the past, the Apple workforce launched Safari 26.0! Is it an enormous deal? I imply, browsers launch new variations on a regular basis, the place they sprinkle in a pair or few new options. They’re, after all, all helpful, however there aren’t normally a number of “huge leaps” between variations. Safari 26 is completely different, although. It introduces a lot of recent stuff. To be exact, it provides: 75 new options, 3 deprecations, and 171 different enhancements.

I’d formally name {that a} huge deal.

The WebKit weblog put up does a tremendous job breaking down every of the brand new (not solely CSS) options. However once more, there are such a lot of that the brand new stuff coming to CSS deserves its personal highlight. So, immediately I wish to test (and in addition attempt) what I feel are essentially the most fascinating options coming to Safari.

If you’re like me and don‘t have macOS to check Safari, you’ll be able to use Playwright as an alternative.

What’s new (to Safari)?

Safari 26 introduces a number of options you could already know from prior Chrome releases. And… I can’t blame Safari for seemingly lagging behind as a result of Chrome is delivery new CSS at a scarily quick tempo. I recognize that browsers stagger releases to allow them to refine issues towards one another. Keep in mind when Chrome initially shipped position-area as inset-area? We bought higher naming between the 2 implementations.

I feel what you’ll discover (as I did) that many of those overlapping options are a part of the larger effort in direction of Interop 2025, one thing WebKit is dedicated to. So, let’s look particularly at what’s new in Safari 26… at the very least that’s new to Safari.

Anchor positioning

Anchor positioning is one among my favourite options (I wrote the information on it!), so I’m so glad it’s arrived in Safari. We at the moment are one step nearer to extensively accessible assist which suggests we’re that a lot nearer to utilizing anchor positioning in our manufacturing work.

With CSS Anchor Positioning, we will connect an absolutely-positioned ingredient (that we could name a “goal”) to a different ingredient (that we could name an “anchor”). This makes creating issues like tooltips, modals, and pop-ups trivial in CSS, though it may be used for a number of layouts.

Utilizing anchor positioning, we will connect any two components, like these, collectively. It doesn’t even matter the place they’re within the markup.

anchor

goal

Heads up: Though the supply order doesn’t matter for positioning, it does for accessibility, so it’s a good suggestion to determine a relationship between the anchor and goal utilizing ARIA attributes for higher experiences that depend on assistive tech.

We register the .anchor ingredient utilizing the anchor-name property, which takes a dashed ident. We then use that ident to connect the .goal to the .anchor utilizing the position-anchor property.

.anchor {
  anchor-name: --my-anchor; /* the ident */
}

.goal {
  place: absolute;
  position-anchor: --my-anchor; /* hooked up! */
}

This positions the .goal on the heart of the .anchor — once more, irrespective of the supply order! If we wish to place it someplace else, the best means is utilizing the position-area property.

With position-area, we will outline a area round the .anchor and place the .goal in it. Consider it like drawing a grid of squares which might be mapped to the .anchor‘s heartprimeproperbackside and left.

For instance, if we want to place the goal on the anchor’s top-right nook, we will write…

.goal {
  /* ... */
  position-area: prime proper;
}

That is only a style since anchor positioning is a world unto itself. I’d encourage you to learn our full information on it.

Scroll-driven animations

Scroll-driven animations hyperlink CSS animations (created from @keyframes) to a component’s scroll place. So as an alternative of operating an animation for a given time, the animation will depend upon the place the person scrolls.

We will hyperlink an animation to 2 varieties of scroll-driven occasions:

  • Linking the animation to a scrollable container utilizing the scroll() operate.
  • Linking the animation to a component’s place on the viewport utilizing the view() operate.

Each of those capabilities are used contained in the animation-timeline, which hyperlinks the animation progress to the kind of timeline we’re utilizing, be it scroll or view. What’s the distinction?

With scroll(), the animation runs because the person scrolls the web page. The best instance is a kind of studying bars that you simply would possibly see develop as you learn down the web page. First, we outline our on a regular basis animation and add it to the bar ingredient:

@keyframes develop {
  from {
    remodel: scaleX(0);
  }
  to {
    remodel: scaleX(1);
  }
}

.progress {
  transform-origin: left heart;
  animation: develop linear;
}

Observe: I’m setting transform-origin to left so it the animation progresses from the left as an alternative of increasing from the middle.

Then, as an alternative of giving the animation a length, we will plug it into the scroll place like this:

.progress {
  /* ... */
  animation-timeline: scroll();
}

Assuming you’re utilizing Safari 26 or the newest model of Chrome, the bar grows in width from left to proper as you scroll down the viewport.

The view() operate is analogous, nevertheless it bases the animation on the ingredient’s place when it’s in view of the viewport. That means, an animation can begin or cease at particular factors on the web page. Right here’s an instance making pictures “pop” up as they enter view.

@keyframes popup {
  from {
    opacity: 0;
    remodel: translateY(100px);
  }
  to {
    opacity: 1;
    remodel: translateY(0px);
  }
}

img {
  animation: popup linear;
}

Then, to make the animation progress because the ingredient enters the viewport, we plug the animation-timeline to view().

img {
    animation: popup linear;
    animation-timeline: view();
}

If we depart like this, although, the animation ends simply because the ingredient leaves the display. The person doesn’t see the entire thing! What we wish is for the animation to finish when the person is in the midst of the viewport so the complete timeline runs in view.

That is the place we will attain for the animation-range property. It lets us set the animation’s begin and finish factors relative to the viewport. On this particular instance, let’s say I need the animation to begin when the ingredient enters the display (i.e., the 0% mark) and finishes just a little bit earlier than it reaches the direct heart of the viewport (we’ll say 40%):

img {
  animation: popup linear;
  animation-timeline: view();
  animation-range: 0% 40%;
}

As soon as once more, scroll-driven animations go means past these two fundamental examples. For a fast intro to all there’s to them, I like to recommend Geoff’s notes.

I really feel safer utilizing scroll-drive animations in my manufacturing work as a result of it’s extra of a progressive enhancement that gained’t break an expertise even when it isn’t supported by the browser. Even so, somebody could choose lowered (or no) animation in any respect, that means we’d higher progressively improve it anyway with prefers-reduced-motion.

The progress() operate

That is one other function we bought in Chrome that has made its method to Safari 26. Humorous sufficient, I missed it in Chrome when it launched a number of months in the past, so it makes me twice as glad to see such a useful function baked into two main browsers.

The progress() operate tells you ways a lot a worth has progressed in a variety between a place to begin and an ending level:

progress(, , )

If the is lower than the , the result’s 0. If the reaches the , the result’s 1. Something in between returns a decimal between 0 and 1.

Technically, that is one thing we will already do in a calc()-ulation:

calc((worth - begin) / (finish - begin))

However there’s a key distinction! With progress(), we will calculate values from blended information varieties (like including px to rem), which isn’t presently potential with calc(). For instance, we will get the progress worth formatted in viewport models from a numeric vary formatted in pixels:

progress(100vw, 400px, 1000px);

…and it’ll return 0 when the viewport is 400px, and because the display grows to 1000px, it progresses to 1. This implies it could typecast completely different models right into a quantity, and as a consequence, we will transition properties like opacity (which takes a quantity or proportion) based mostly on the viewport (which is a distance size).

There’s one other workaround that accomplishes this utilizing tan() and atan2() capabilities. I’ve used that method earlier than to create easy viewport transitions. However progress() drastically simplifies the work, making it way more maintainable.

Living proof: We will orchestrate a number of animations because the display measurement adjustments. This subsequent demo takes one of many demos I made for the article about tan() and atan2(), however swaps that out with progress(). Works like a attraction!

That’s a reasonably wild instance. One thing extra sensible is perhaps decreasing a picture’s opacity because the display shrinks:

img {
  opacity: clamp(0.25, progress(100vw, 400px, 1000px), 1);
}

Go forward and resize the demo to replace the picture’s opacity, assuming you’re taking a look at it in Safari 26 or the newest model of Chrome.

I’ve clamp()-ed the progress() between 0.25 and 1. However, by default, progress() already clamps the between 0 and 1. In response to the WebKit launch notes, the present implementation isn’t clamped by default, however upon testing, it does appear to be. So, in the event you’re questioning why I’m clamping one thing that’s supposedly clamped already, that’s why.

An unclamped model could come sooner or later, although.

Self-alignment in absolute positioning

And, hey, test this out! We will align-self and justify-self content material inside absolutely-positioned components. This isn’t as huge a deal as the opposite options we’ve checked out, nevertheless it does have a useful use case.

For instance, I typically wish to place an absolutely-positioned ingredient straight within the heart of the viewport, however inset-related properties (i.e., primeproperbacksideleft, and so on.) are relative to the ingredient’s top-left nook. Which means we don’t get completely centered with one thing like this as we’d anticipate:

.absolutely-positioned {
  place: absolute;
  prime: 50%;
  left: 50%;
}

From right here, we might translate the ingredient by half to get issues completely centered. However now we’ve the heart key phrase supported by align-self and justify-self, that means fewer shifting items within the code:

.absolutely-positioned {
  place: absolute;
  justify-self: heart;
}

Weirdly sufficient, I seen that align-self: heart doesn’t appear to heart the ingredient relative to the viewport, however as an alternative relative to itself. I came upon that may use the anchor-center worth to heart the ingredient relative to its default anchor, which is the viewport on this particular instance:

.absolutely-positioned {
  place: absolute;
  align-self: anchor-center;
  justify-self: heart;
}

And, after all, place-self is a shorthand for the align-self and justify-self properties, so we might mix these for brevity:

.absolutely-positioned {
  place: absolute;
  place-self: anchor-center heart;
}

What’s new (for the online)?

Safari 26 isn’t nearly maintaining with Chrome. There’s a number of thrilling new stuff in right here that we’re getting our fingers on for the primary time, or that’s refined from different browser implementations. Let’s have a look at these options.

The constrast-color() operate

The constrast-color() isn’t new by any means. It’s really been in Safari Expertise Preview since 2021 the place it was initially referred to as color-contrast(). In Safari 26, we get the up to date naming in addition to some polish.

Given a sure colour worth, contrast-color() returns both white or black, whichever produces a sharper distinction with that colour. So, if we had been to offer coral as the colour worth for a background, we will let the browser determine whether or not the textual content colour is extra contrasted with the background as both white or black:

h1 {
  --bg-color: coral;
  background-color: var(--bg-color);
  colour: contrast-color(var(--bg-color));
}

Our personal Daniel Schwarz lately explored the contrast-color() operate and located it’s really not that nice at figuring out the perfect distinction between colours:

Undoubtedly, the primary shortcoming is that contrast-color() solely resolves to both black or white. When you don’t need black or white, properly… that sucks.

It sucks as a result of there are instances the place neither white nor black produces sufficient distinction with the offered colour to fulfill WCAG colour distinction pointers. There may be an intent to increase contrast-color() so it could return extra colour values, however there nonetheless can be considerations about how precisely contrast-color() arrives on the “greatest” colour, since we might nonetheless must think about the font’s width, measurement, and even household. All the time test the precise distinction!

So, whereas it’s nice to lastly have constrat-color(), I do hope we see enhancements added sooner or later.

Fairly textual content wrapping

Safari 26 additionally introduces text-wrap: fairly, which is fairly (get it?) easy: it makes paragraphs wrap in a prettier means.

You could keep in mind that Chrome shipped this again in 2023. However take discover that there’s a fairly (OK, that’s the final time) huge distinction between the implementations. Chrome solely avoids typographic orphans (brief final strains). Safari does extra to prettify the way in which textual content wraps:

  • Prevents brief strains. Avoids single phrases on the finish of the paragraph.
  • Improves rag. Retains every line comparatively the identical size.
  • Reduces hyphenation. When enabled, hyphenation improves rag but in addition breaks phrases aside. Usually, hyphenation needs to be stored to a minimal.

The WebKit weblog will get into a lot better element in the event you’re inquisitive about what issues they put into it.

Comparing the same paragraph of text wrapping in Safari and Chrome using text-wrap: pretty. They produce different results.
Safari takes extra actions to make sure “fairly” textual content wrapping, together with the general ragging alongside the textual content.

That is just the start!

I feel these are all of the CSS options coming to Safari that you must look out for, however I don’t need you to assume they’re the one options within the launch. As I discussed on the prime, we’re speaking about 75 new Net Platform options, together with HDR Pictures, assist for SVG favicons, logical property assist for overflow properties, margin trimming, and far, way more. It’s price perusing the full launch notes.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles