Safari 26 Ships a Big Batch of CSS Features
Apple’s Safari 26.0 release is a substantial one, adding 75 new web platform features alongside three deprecations and 171 other improvements. Among those additions are several CSS capabilities that developers have been waiting on, including anchor positioning, scroll-driven animations, and the progress() function.
Many of these features are part of the broader Interop 2025 effort, meaning they align with implementations already shipping in Chrome. That cross-browser consistency is good news for production use. Here’s a look at the CSS highlights worth paying attention to.
CSS Anchor Positioning Arrives
Safari 26 now supports CSS anchor positioning, which lets you attach an absolutely-positioned element (the “target”) to any other element (the “anchor”). This works regardless of source order in the markup, though you should still establish an ARIA relationship between the two for assistive technology users.
To set this up, register the anchor element with the anchor-name property using a dashed ident:
.anchor {
anchor-name: --my-anchor; /* the ident */
}
.target {
position: absolute;
position-anchor: --my-anchor; /* attached! */
}
Then attach the target to that anchor with the position-anchor property. The position-area property defines which region around the anchor the target should occupy, using a grid-like mapping of the anchor’s center, top, right, bottom, and left edges. For instance, placing the target at the anchor’s top-right corner looks like this:
.target {
/* ... */
position-area: top right;
}
Anchor positioning enables tooltips, popovers, and other overlay UI without JavaScript, and it opens the door to more creative layouts where elements visually connect regardless of DOM order.
Scroll-Driven Animations
Scroll-driven animations tie CSS @keyframes animations to scroll position rather than time. Safari 26 supports two ways to drive these animations via the animation-timeline property:
scroll()— links the animation to a scrollable container’s scroll position.view()— links the animation to when an element enters and exits the viewport.
A simple example of scroll() is a reading progress bar that grows as the user scrolls down the page. Define a standard animation and add it to the bar:
@keyframes grow {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
.progress {
transform-origin: left center;
animation: grow linear;
}
Then substitute the animation’s time-based duration with the scroll timeline:
.progress {
/* ... */
animation-timeline: scroll();
}
The view() function behaves similarly but runs the animation based on the element’s visibility in the viewport. For example, images can “pop” into view as they scroll into frame:
@keyframes popup {
from {
opacity: 0;
transform: translateY(100px);
}
to {
opacity: 1;
transform: translateY(0px);
}
}
img {
animation: popup linear;
}
Hook the animation to the element’s viewport entry:
img {
animation: popup linear;
animation-timeline: view();
}
By default, the animation would end just as the element leaves the screen, so you can control its start and end points with the animation-range property. To have the animation finish when the element reaches the middle of the viewport, set the range accordingly:
img {
animation: popup linear;
animation-timeline: view();
animation-range: 0% 40%;
}
Since scroll-driven animations degrade gracefully in unsupported browsers, they work well as progressive enhancements. Pair them with a prefers-reduced-motion query to respect users who want less animation.
The progress() Function
Also new in Safari 26 is the progress() function, which calculates how far a value has traveled between a start and end point:
progress(<value>, <start>, <end>)
Values below the start resolve to 0; values at or beyond the end resolve to 1. Anything in between returns a decimal. This is achievable with calc(), but progress() has a key advantage: it operates on mixed data types. You can compute progress from a pixel range and output a unitless number, something calc() cannot do directly:
progress(100vw, 400px, 1000px);
That capability lets you drive properties like opacity based on viewport size, which previously required workarounds using tan() and atan2(). With progress(), the code is simpler and easier to maintain. One practical use is fading an image as the viewport shrinks:
img {
opacity: clamp(0.25, progress(100vw, 400px, 1000px), 1);
}
Note that the WebKit release notes say the current implementation is not clamped by default, but in practice it appears to clamp the value between 0 and 1 anyway, which is why you may see an explicit clamp() in examples. An unclamped version is being discussed for a future spec update.
Self-Alignment for Absolutely Positioned Elements
Safari 26 also adds support for align-self and justify-self on absolutely-positioned elements. This simplifies centering, since inset properties like top and left are relative to the element’s top-left corner. Previously, perfect centering required a transform hack:
.absolutely-positioned {
position: absolute;
top: 50%;
left: 50%;
}
With the new keywords, you can center more directly:
.absolutely-positioned {
position: absolute;
justify-self: center;
}
One quirk surfaced during testing: align-self: center doesn’t always center relative to the viewport. Using anchor-center centers the element relative to its default anchor (the viewport in this case):
.absolutely-positioned {
position: absolute;
align-self: anchor-center;
justify-self: center;
}
The place-self shorthand combines both properties for concise code:
.absolutely-positioned {
position: absolute;
place-self: anchor-center center;
}
Enter contrast-color()
After a long gestation in Safari Technology Preview under the name color-contrast(), the contrast-color() function is now in a stable release with its updated naming. Given a color, it returns either black or white, whichever has higher contrast with that color. For example, you can declare a background color and let the browser pick the text color:
h1 {
--bg-color: coral;
background-color: var(--bg-color);
color: contrast-color(var(--bg-color));
}
While useful, the function has limitations. It only resolves to black or white, and there are cases where neither produces sufficient contrast to meet WCAG guidelines. The spec may eventually allow additional colors, but you should still verify actual contrast ratios in your designs—particularly considering font weight, size, and family.
Smarter Text Wrapping With text-wrap: pretty
Safari 26 implements text-wrap: pretty, which improves paragraph typography beyond Chrome’s version. Chrome’s implementation simply avoids orphans (a single word on the last line). Safari’s version does more:
- Prevents short lines — avoids single words dangling at the end of a paragraph.
- Improves rag — evens out line lengths for a cleaner right edge.
- Reduces hyphenation — minimizes word breaks when hyphenation is enabled.

Beyond the CSS Headliners
The CSS features above are the most noteworthy, but Safari 26 ships much more. Highlights from the full release notes include HDR image support, SVG favicons, logical property support for overflow, and margin trimming. With all 75 new features, this release is worth a closer look. The complete WebKit release notes document everything in detail.



