This release includes WebKit changes between: 307619@main…308417@main.
<use> elements referencing <symbol> elements inside an <img> were incorrectly included as unnamed images in VoiceOver’s Images web rotor. (308126@main) (98999595)aria-owned rows and their cells in grids and tables. (308087@main) (168770938)aria-labelledby did not use the native label’s geometry when both the control and its ARIA label had no visible bounding box. (307727@main) (170518900):open pseudo-class for <input> elements. (308148@main) (170804152)inset box-shadow was incorrectly positioned on table cells with collapsed borders. (307661@main) (169254286)display: grid subgrids inside grid-lanes containers incorrectly contributed their item intrinsic sizes to the parent’s track sizing algorithm. (308253@main) (170168798)inline-block elements so that when overflow is not visible, the baseline is correctly set to the bottom margin edge. (307718@main) (170575015)min-height and min-width constraints in certain configurations. (308212@main) (170765025)fit-content, min-content, max-content) incorrectly resolved against the containing block’s height instead of being treated as auto. (308226@main) (171179193)execCommand('FormatBlock') did not preserve inline styles of replaced block elements, causing text formatting to be lost when pasting content. (308365@main) (157657531)text-indent flickered or was ignored on contenteditable elements while typing. (307646@main) (170280101)readonly date <input> could still be edited via keyboard using the date picker. (307934@main) (169488939)<mo> element attributes did not trigger a relayout. (308014@main) (170907029)<mprescripts> element within <mmultiscripts> layout. (308013@main) (170909975)<none> and <mprescripts> elements were not laid out as <mrow> elements in MathML. (308050@main) (170940035)VideoFrame constructor did not handle the video color range correctly for NV12 (I420 BT601) video frames. (307649@main) (170299037)change event was not fired on <input> and <textarea> elements when they lost focus while another application was in the foreground. (308203@main) (98526540)dragend event had incorrect coordinates when dragging within a nested <iframe>. (308216@main) (170750013)compileOptions parameter required to enable JS String builtins. (308419@main) (170989896)RTCPeerConnection.addIceCandidate() did not reject when the connection was already closed. (307702@main) (170470988)This release includes WebKit changes between: 306596@main…307618@main.
:open pseudo-class, which matches form elements and other elements in an open state. (307295@main) (170108337)padding-inline-end, aligning with the CSS Overflow specification. (307212@main) (144312078)<select> element, allowing custom styling and content in <select> dropdowns using appearance: base-select. (307548@main) (170328089)iso-8859-2, windows-1250, and gbk. (306768@main) (169566553)rowspan="0" cells. (306891@main) (146056348)fx and fy attributes on SVGRadialGradientElement to 50% to align with the SVG2 specification. (306811@main) (169645572)SVGAnimatedRect.baseVal to ignore invalid values set on the viewBox attribute, such as negative width or height, aligning with Firefox and Chrome. (307463@main) (170214971)ReadableStream.from() to create a ReadableStream from an async iterable or iterable. (306786@main) (169605740)ReadableStream objects via postMessage(). (307068@main) (169950592)NavigateEvent.canIntercept to correctly return false when navigating to a URL with a different port, aligning with the Navigation API specification. (307316@main) (169845691)RTCRtpSynchronizationSource.timestamp to use the correct time base, aligning with the WebRTC specification. (307063@main) (169679084)RTCIceCandidate.toJSON() to include the usernameFragment field in the serialized output. (306845@main) (169679947)RTCRtpTransceiver.setCodecPreferences() to accept codec entries regardless of the mimeType case, aligning with Firefox and the specification. (307062@main) (169789074)RTCRtpSender to allow a maxFramerate encoding parameter value of 0, aligning with the specification and other browsers. (307034@main) (169863687)Making your website work in every browser can be a challenge, especially if browser engines have implemented the same web technology in slightly different ways. The Interop Project tackles this challenge by bringing the major browser engines together to improve the same set of features during the same year. Each feature is judged on whether or not it fully aligns with its official web standard — the formal technical specifications that define how each web technology should work. This helps accelerate progress toward a more reliable, consistent platform to build on.
Safari has already implemented many of the features included in Interop 2026. In fact, we were the first browser to ship contrast-color(), Media pseudo-classes, shape(), and Scoped Custom Element Registries. Plus, we have support for Anchor Positioning, Style Queries, Custom Highlights, Scroll Snap, View Transitions and much more. We’re excited that these technologies are being included as focus areas in Interop 2026, ensuring they get implemented across all browsers and any remaining interoperability gaps are closed.
We will also be focused on adding support for the following features: advanced attr(), the getAllRecords() method for IndexedDB, WebTransport, and the JavaScript Promise Integration API for Wasm. Together, these four areas make up 20% of the Interop 2026 score. They are exciting new features that solve real needs.
The Interop Project measures interoperability through Web Platform Tests — automated tests that check whether browsers conform to web standards. Interop 2026 is ambitious, covering twenty focus areas. Fifteen are brand new. And five are carryovers from Interop 2025.
attr()contrast-color()getAllRecords() for IndexedDBshape()Anchor positioning is a carryover from Interop 2025, where significant progress was made to empower developers to position elements relative to each other. This year’s focus will be on clarifying the spec, resolving test issues, and increasing the reliability of this powerful layout feature.
The CSS attr() function lets you bridge the gap between structural data and visual presentation by pulling values directly from HTML attributes into your CSS, making styles more dynamic and context-aware without the overhead of JavaScript. While attr() has long been supported for the content property, advanced attr() extends it to work across all CSS properties with type conversion — letting you use HTML attribute values as colors, lengths, angles, and other data types. Now that security concerns have been worked through in the specification, browser makers are united in our excitement to ship this long-awaited capability with strong interoperability.
Style queries let you apply styles conditionally, based on the value of a custom property (aka, variable) as defined at a certain container. Similar to how Container size queries let your CSS respond to the size of the container, style queries let it respond to theme values, state flags, and other contextual data.
@container style(--theme: dark) {
.card {
background: #1a1a1a;
color: #ffffff;
}
}
Style queries started shipping in recent years, including in Safari 18.0. Interop 2026 will help ensure this powerful tool works consistently everywhere.
The contrast-color() function in CSS returns a color — either black or white. It puts the burden on the browser to choose whichever has higher contrast with the color specified in the function.
.button {
background: var(--brand-color);
color: contrast-color(var(--brand-color));
}
By having the browser make the choice, you can architect your design system in a simpler fashion. You don’t need to manually define every color pairing. Safari and Firefox both shipped support in 2025, and now Interop 2026 will ensure this powerful function works consistently across all browsers.
Note, contrast-color() does not magically solve all accessibility concerns. Read about all the details in How to have the browser pick a contrasting color in CSS.
The CSS zoom property scales an element and its contents, affecting layout and making the element take up more (or less) space. Unlike transform: scale(), which is purely visual, zoom changes how the element participates in layout.
.card {
zoom: 1.5; /* Element is 150% larger and takes up more space */
}
While zoom was supported in browsers for years as a non-standard property, it’s been plagued by inconsistencies in edge cases and how it interacts with other layout features. Now that it’s standardized, CSS zoom returns as a focus area in Interop 2026, continuing from 2025.
The CSS Custom Highlight API lets you style arbitrary text ranges without adding extra elements to the DOM. Using JavaScript, you create a highlight range, then style it with the pseudo-elements.
The ::highlight() pseudo-element is perfect for highlighting in-page search results, customizing syntax highlighting in code editors, creating an app that allows collaborative editing with user cursors, or any situation where you need to visually mark text without changing the document structure. The ::target-text pseudo-element styles the text that’s scrolled to when a user taps a link with a text fragment.
With implementations progressing across browsers, Interop 2026 ensures these highlighting capabilities work consistently, giving you reliable tools for text-based interactions.
The <dialog> element and popover attribute have transformed how developers build overlays on the web. Dialog was part of Interop 2022 and Popover was in Interop 2024. This year, three recent enhancements to these features make up this focus area for Interop 2026.
The closedby attribute lets you control how users can dismiss dialogs:
<dialog closedby="any">
<!-- Can be closed by clicking outside or pressing Escape -->
</dialog>
The popover="hint" attribute creates subordinate popovers that don’t dismiss other auto popovers — perfect for tooltips:
<div popover="hint" id="tooltip">
This tooltip won’t close the menu!
</div>
The :open pseudo-class matches elements with open states, working with <dialog>, <details>, and <select>:
dialog:open {
animation: slideIn 0.3s;
}
Together, these additions make building accessible, user-friendly UI overlays easier than ever.
The fetch() method is getting three new powerful capabilities for handling uploads and partial content.
ReadableStream request bodies enable true streaming uploads, letting you upload large files or real-time data without loading everything into memory first:
await fetch('/upload', {
method: 'POST',
body: readableStream,
duplex: 'half'
});
Enhanced FormData support improves multipart uploads and responses.
Range header support allows partial content requests, essential for video streaming and resumable downloads:
fetch('/video.mp4', {
headers: { 'Range': 'bytes=0-1023' }
});
These enhancements bring fetch() up to par with more specialized APIs, reducing the need for custom solutions.
IndexedDB is a low-level API that lets you store large amounts of structured data in the browser, including files and blobs. It’s been supported in browsers for many years.
Now, IndexedDB is getting a significant performance boost with the new getAllRecords() methods for IDBObjectStore and IDBIndex. These methods allow you to retrieve records in batches and in reverse order:
const records = await objectStore.getAllRecords({
query: IDBKeyRange.bound('A', 'M'),
count: 100,
direction: 'prev'
});
It’s just this new method that’s being included in Interop 2026. The score only reports the percentage of getAllRecords() tests that are passing — not all IndexDB tests.
WebAssembly has opened the door for running high-performance applications in the browser — games, productivity tools, scientific simulations, and more. But there’s been a fundamental mismatch. Many of these applications were originally written for environments where operations like file I/O or network requests are synchronous (blocking), while the web is fundamentally asynchronous.
The JavaScript Promise Integration API (JSPI) bridges this gap. It lets WebAssembly code that expects synchronous operations work smoothly with JavaScript’s Promise-based async APIs, without requiring you to rewrite the entire application. This means you can port existing C, C++, or Rust applications to the web more easily, unlocking a wider range of software that can run in the browser.
Interop 2026 will ensure JSPI works consistently across browsers, making WebAssembly a more viable platform for complex applications.
We’ve proposed media pseudo-classes for inclusion in the Interop Project for many years in a row. We are excited that it’s being included this year!
Seven CSS pseudo-classes let you apply CSS based on the playback state of <audio> and <video> elements:
:playing — media is currently playing:paused — media is paused:seeking — user is seeking to a new position:buffering — media is buffering:stalled — playback has stalled:muted — audio is muted:volume-locked — volume cannot be changedThese all shipped in Safari many years ago, but without support in any other browser, most developers don’t use them — or even know they exist. Instead developers need JavaScript to sync UI state with media playback state.
It’s far simpler and more efficient to use media state pseudo-classes in CSS.
video:buffering::after {
content: "Loading...";
}
audio:muted {
opacity: 0.5;
}
They are especially powerful combined with :has(), since it unlocks the ability to style anything on the page based on playback state, not just elements that are descendants of the media player.
article:has(video:playing) {
background-color: var(--backgroundColor);
color: contrast-color(var(--backgroundColor));
transition: background-color 0.5s ease;
}
Learn more about the power of :has() in Using :has() as a CSS Parent Selector and much more.
If you’ve built single-page applications, you may have experienced the pain of managing navigation state with history.pushState() and popstate events. Navigation API gives you a cleaner, more powerful way to intercept and control navigation.
This focus area is a continuation of Interop 2025, where significant progress was made to empower developers to initiate, intercept, and modify browser navigation actions. This year continues work on interoperability, to get the overall score up from the 92.3% test pass result during Interop 2025. Plus, there’s one new feature being added — the precommitHandler option. It lets you defer navigation until critical resources are ready, preventing jarring flashes of incomplete content.
navigation.addEventListener('navigate', (e) => {
e.intercept({
async precommitHandler() {
// Load critical resources before commit
await loadCriticalData();
},
async handler() {
// Render the new view
renderPage();
}
});
});
Interop 2026 will ensure Navigation API works reliably across browsers, a solid foundation for web applications.
Working with web components, you may have run into a frustrating limitation: the global customElements registry only allows one definition per tag name across your entire application. When two different libraries both define a <my-button> component, they conflict.
The CustomElementRegistry() constructor solves this by letting you create scoped registries. Different parts of your application — or different shadow roots — can have their own definitions for the same tag name.
const registry = new CustomElementRegistry();
registry.define('my-button', MyButtonV2);
shadowRoot.registry = registry;
This is especially valuable for microfrontends, component libraries, and any situation where you’re integrating third-party web components.
Safari 26.0 was the first browser to ship Scoped custom element registries. Inclusion in Interop 2026 will help ensure this capability works consistently across all browsers.
Scroll-driven animations let you more easily create animations that respond to scroll position, now entirely in CSS. As a user scrolls, the animation progresses — no JavaScript needed. You can build scroll-triggered reveals, progress indicators, parallax effects, and interactive storytelling experiences.
Define animations with standard CSS keyframes, then connect them to scroll using animation-timeline:
.reveal {
animation: fade-in linear forwards;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
Use view() to trigger animations as elements enter and exit the viewport, or scroll() to tie animations to a scrolling container’s position. Learn much more in A guide to Scroll-driven Animations with just CSS.
We shipped support for scroll-driven animations in Safari 26.0. Interop 2026 will help ensure this feature works consistently across all browsers.
CSS Scroll Snap controls the panning and scrolling behavior within a scroll container, creating carousel-like experiences:
.carousel {
scroll-snap-type: x mandatory;
overflow-x: scroll;
}
.carousel > * {
scroll-snap-align: center;
}
Scroll Snap has been supported in all modern browsers for many years. But like many of the older CSS specifications, multiple rounds of changes to the specification while early versions were already shipping in browsers created a deep lack of interoperability. With a far more mature web standard, it’s time to circle back and improve interoperability. This is the power of the Interop Project — focusing all the browser teams on a particular feature, and using automated tests to find inconsistencies and disagreements.
For years, when you wanted to create a complex clipping path to use with clip-path or shape-outside you’ve been limited to polygon(), which only supports straight lines, or SVG paths, which aren’t responsive to element size changes.
Now, the shape() function lets you create complex shapes with path-like commands (move, line, curve). It gives you the best of both worlds — curves like SVG paths, but with percentage-based coordinates that adapt as elements resize.
.element {
clip-path: shape(
from 0% 0%,
line to 100% 0%,
line to 100% 100%,
curve to 0% 100% via 50% 150%,
close
);
}
We shipped support for the shape() function in Safari 18.4. And we look forward to Interop 2026 improving browser implementations so you can confidently use it to render of complex, responsive curves.
View Transitions was a focus area in Interop 2025, narrowly defined to include same-document view transitions and view-transition-class. These features allow for smooth, animated transitions between UI states within a single page, as well as flexible control over styling those transitions.
While Safari finished Interop 2025 with a score of 99.2% for view transitions, the overall interoperability score is at 90.8% — so the group decided to continue the effort, carrying over the tests from 2025.
For Interop 2026, the focus area expands to also include cross-document view transitions. This allows you to create smooth, animated transitions in the moments between pages as users navigate your site, rather than an abrupt jump when new page loads. Cross-document view transitions shipped in Safari 18.2. Learn more about it in Two lines of Cross-Document View Transitions code you can use on every website today.
Web compatibility refers to whether or not a real world website works correctly in a particular browser. When a site works in one browser, but not another — that’s a “compat” problem. This focus area is made up of a small collection of Web Platform Tests selected because the fact they fail in some browsers causes real websites to not work in other browsers — thus creating problems for both web developers and users.
Each time Web Compat has been a focus area as part of the Interop Project, it’s targeted a different set of compat challenges. This year, Interop 2026’s web compatibility work includes:
user-select, rather than just -webkit-user-select. It controls whether users can select text. WebRTC (Web Real-Time Communication) enables real-time audio, video, and data communication directly between browsers, without requiring plugins or intermediate servers. You can build video conferencing apps, live streaming platforms, peer-to-peer file sharing, and collaborative tools.
Having reached a 91.6% pass rate, WebRTC continues as a focus area in 2026, building on the progress made during Interop 2025. We’re looking forward to fixing the long tail of interop issues of the main spec for WebRTC.
WebTransport provides a modern way to transmit data between client and server using the HTTP/3 protocol. It gives you low-latency bidirectional communication with multiple streams over a single connection. You get both unreliable datagram support (like UDP) for speed and reliable stream support (like TCP) for guaranteed delivery.
const transport = new WebTransport('https://example.com/endpoint');
await transport.ready;
const stream = await transport.createBidirectionalStream();
// Stream data efficiently
WebTransport is ideal for gaming, real-time collaboration tools, and applications where you need more control than WebSocket provides but don’t want to manage WebRTC’s complexity. Being part of Interop 2026 ensures WebTransport works consistently across all browsers, making it a reliable choice for real-time data transmission.
In addition to the focus areas, the Interop Project includes four investigation areas. These are projects where teams gather to assess the current state of testing infrastructure and sort through issues that are blocking progress.
Continuing from previous years, the Accessibility Testing investigation aims to work towards generating consistent accessibility trees across browsers. This effort will improve the WPT testing infrastructure for accessibility on top of the foundation from Interop 2024. This work ensures that accessibility features are reliable and consistent, helping developers create more inclusive web experiences.
JPEG XL is a next-generation raster graphics format that supports animation, alpha transparency, and lossy as well as lossless compression. We shipped support for it in Safari 17.0. This investigation will focus on making the feature properly testable by developing comprehensive test suites, opening up the possibility that JPEG XL could be a focus area in the future.
The Mobile Testing investigation continues work started in 2025. This year, we will focus on improving infrastructure for mobile-specific features like dynamic viewport changes which are crucial for building responsive mobile web experience that billions of users rely on every day.
Continuing from 2025, the WebVTT investigation addresses a critical challenge facing the web platform. Developers cite WebVTT’s inconsistent behavior across browsers as a major reason for choosing other subtitling and captioning solutions. Our investment in WebVTT last year primarily consisted of validating and fixing the existing test suite, as well as making any necessary spec changes along the way. We are excited to continue that effort this year to ensure synchronized text tracks and closed captioning work seamlessly across the web.
Interop 2026 brings together twenty focus areas that matter to you as a web developer. Some, like attr() and contrast-color(), give you more flexible ways to architect your CSS. Others, like Scroll-Driven Animations and View Transitions, let you create smoother, more engaging experiences without reaching for JavaScript. Features like WebTransport and the Navigation API give you more powerful tools for building modern web applications.
Just as important are the focus areas working to fix long-standing inconsistencies — ensuring Scroll Snap works reliably, bringing all browsers up to speed on shape(), and solving real-world compatibility problems that have been frustrating developers and breaking sites.
The WebKit team is committed to making these features work consistently across all browsers. Whether you’re building a design system, a single-page application, a video streaming platform, or anything in between, Interop 2026 is working to give you a more reliable foundation to build on.
Here’s to another year of making the web better, together!
]]>This release includes WebKit changes between: 305774@main…306595@main.
aria-labelledby to correctly use their assigned slotted content for accessible names and ignore hidden slotted nodes. (305882@main) (114500560)<meter> element to have consistent labels between aria-label and title attributes. (305883@main) (127460695)display: contents and content in a shadow root to have their content properly read when referenced by aria-labelledby. (305918@main) (129361833)aria-labelledby to use the checkbox name instead of its value when the checkbox name comes from an associated <label> element. (305894@main) (141564913)aria-controls or aria-expanded and the hidden attribute to no longer appear in VoiceOver’s Form Control menu. (305902@main) (162783041)::first-letter text not being exposed in the accessibility tree when no other text accompanies it. (305884@main) (168458291):heading pseudo-class selector. (306151@main) (158759228)@scope not being applied to <input> and <textarea> elements. (306129@main) (168101378)flow-tolerance changes to trigger relayout for display: grid-lanes. (306093@main) (168711707)offsetX and offsetY for SVG elements to use the outermost SVG as the base for coordinate calculation. (305993@main) (168548585)source attribute on ToggleEvent interface. (306152@main) (152580641)X-Frame-Options to only strip tab or space characters, not vertical tabs. (306279@main) (126915315)<marquee> elements causing incorrect table width calculations. (306059@main) (99826593)visibility: collapse on columns. (305997@main) (168556786)color-interpolation attribute for SVG gradients. (305921@main) (87294645):visited link color to properly propagate to SVG through currentColor. (306387@main) (98776770)SVGTransformList to properly allow attribute removal. (306162@main) (117840533)ReadableStream objects using for await...of loops. (305808@main) (168049382)srgb-linear and display-p3-linear to PredefinedColorSpace. (306563@main) (169340732)toolbar.visible, statusbar.visible, menubar.visible) to return static values per the HTML specification for privacy and interoperability. (306391@main) (166554327)layerX and layerY to return correct values with CSS transforms. (306300@main) (168968832)runtime.getDocumentId() web extension API. (305912@main) (168060269)targetLatency attribute in WebRTC. (306062@main) (168225793)Now in Safari 26.3 in visionOS, fullscreen video playback automatically dims the user’s surroundings to help put the focus on content.

Safari 26.3 supports Zstandard (Zstd), a compression algorithm you can use to make your website’s files smaller before sending them to browsers. Like gzip and Brotli, it compresses text-based assets — HTML, CSS, JavaScript, JSON, and SVG — so less data travels over the network.
Zstandard decompresses quickly, reducing the workload on users’ devices. It also compresses fast enough to do on-the-fly, whereas Brotli is typically pre-compressed during your build process.
To use it, configure your server to compress responses with Zstandard and send the Content-Encoding: zstd header. Servers will automatically fall back to other compression methods for browsers that don’t have support yet.
Zstandard support is available in Safari 26.3 on iOS 26.3, iPadOS 26.3, visionOS 26.3, and macOS Tahoe 26.3 — and not in Safari 26.3 on earlier versions of macOS. This is because support comes from the system networking stack used by Safari.
When building single-page applications with the Navigation API, you might need a reliable way to cancel ongoing work when a navigation gets interrupted. Maybe the user clicked another link before the previous navigation finished, they hit the back button, or your code called navigation.navigate() again. Whatever the reason, you don’t want to keep processing a navigation that’s no longer relevant.
In Safari 26.3, the Navigation API exposes a AbortSignal on NavigateEvent which triggers when the navigation is aborted, giving you a standard way to clean up and cancel work:
navigation.addEventListener('navigate', (event) => {
event.intercept({
async handler() {
const response = await fetch('/api/data', {
signal: event.signal // Automatically cancels if navigation is aborted
});
const data = await response.json();
renderContent(data);
}
});
});
If the user navigates away before the fetch completes, the request automatically cancels. You can also listen to the signal’s abort event to clean up other resources like timers or animations.
This gives you fine-grained control over what happens when navigations don’t complete, helping you avoid memory leaks and unnecessary work.
Along with the new features, WebKit for Safari 26.3 includes additional improvements to existing features.
position-try box was inside a display: none ancestor. (163691885)display: block to display: none cause position jumps during animation. (163862003)fixed-positioned boxes using position-area were incorrectly included in the scrollable containing block calculation. (164017310)text-decoration: underline was rendered too high when text-box-trim was applied to the root inline box. (165945326)widows and text-indent properties are applied cause an incorrect indent on the portion of the paragraph that flows into the next column. (165945497)move, all-scroll, ew-resize, and ns-resize did not display correctly. (166731882)button.circular elements. (164259201)iframe videos on macOS. (164484608)sourceBuffer content is removed and re-added causing the seek to not complete. (165628836)<img> elements containing HDR JPEGs with gain maps would incorrectly render as SDR. (163517157)We love hearing from you. To share your thoughts, find us online: Jen Simmons on Bluesky / Mastodon, Saron Yitbarek on BlueSky / Mastodon, and Jon Davis on Bluesky / Mastodon. You can follow WebKit on LinkedIn.
If you run into any issues, we welcome your bug report. Filing issues really does make a difference.
You can also find this information in the Safari release notes.
]]>
Each year, the Interop project chooses its focus areas through a collaborative process with proposals, research into what web developers need, and debates about priorities. For Interop 2025, our team advocated for including focus areas that we knew would require significant engineering investment from WebKit — because we knew those areas would make a real difference to you. The results show that commitment paid off. Safari made the largest jump of any browser this year, climbing from 43 to 99.
As always, this year’s focus areas were chosen based on developer feedback, including results from the State of CSS survey, and we’re proud of how much ground we covered. The 19 focus areas touched nearly every corner of the platform. On the CSS and UI side, the project tackled Anchor Positioning, View Transitions, @scope, backdrop-filter, text-decoration, Writing modes, Layout (both Flexbox and Grid, continued from prior years), and the <details> element. For APIs and platform features, we worked on the Navigation API, Storage Access API, URLPattern, Modules, the scrollend event, WebRTC, and WebAssembly. And on the health and compatibility front, there was focused work on Core Web Vitals, Pointer and Mouse events, removing Mutation events, and general web compatibility. Five investigation areas — accessibility testing, Gamepad API testing, mobile testing, privacy testing, and WebVTT — laid groundwork for future Interop cycles.
We want to highlight three focus areas that were especially meaningful this year.
view-transition-class CSS property for flexible styling of those transitions. We shipped support in fall 2024, in Safari 18.0 and Safari 18.2. Web developers are excited about View Transitions! This extra attention on interoperability across browsers means it’s ready for you to use. history.pushState() — gives single-page applications proper navigation handling with interception, traversal, and entries. We shipped support in Safari 26.2, and we’re glad to see it arrive interoperably from the start.
The graph above tells the story of the year: every browser engine invested heavily, and the lines converge at the top. That convergence is what makes the Interop project so valuable — the shared progress that means you can write code once and trust that it works everywhere.
We want to thank our colleagues across the industry who made this possible. Interoperability is one of the foundational strengths of the web, and we remain committed to this collaboration. You can explore the full results, including scores for each individual focus area, on the Interop 2025 dashboard.
]]>This release includes WebKit changes between: 305084@main…305413@main.
text-combine-upright to properly ignore letter-spacing when composing text horizontally, aligning with the CSS Writing Modes specification. (305116@main) (116562622)background-blend-mode was not applied correctly when combined with background-clip: text. (305118@main) (120901898)@starting-style entry animations were only applied on the first transition, especially when interacting with anchor positioning or position fallbacks. (305371@main) (163928932)colspan spans mixed percentage and auto-width columns to properly respect percentage constraints. (305120@main) (165561401)shape-outside did not update correctly after web fonts loaded. (305299@main) (166336491)writing-mode. (305110@main) (167220730)fit-content and percentage-based dimensions. (305319@main) (167221488)counter-* properties serialization order. (305086@main) (167518994)outline-width and outline-offset to follow updated computed style resolution rules. (305153@main) (167618367)border-*-width properties. (305212@main) (167689519)column-rule-width property. (305240@main) (167725940)border-*-width, outline-width, and column-rule-width so they now pixel snap correctly during CSS animations and transitions. (305272@main) (167763497)input[type="search"] fields with appearance: none incorrectly reserved space for the datalist dropdown button. (305314@main) (166754216)min(), max(), and clamp() math functions in the sizes attribute of <img> elements. (305226@main) (167526292)about:blank frames were incorrectly treated as self-referencing, preventing them from loading. (305404@main) (148373033)accept="image/*" is specified. (305283@main) (166124206)<video> poster images were incorrectly double-scaled when zoom was applied by using the cached intrinsic poster size without reapplying zoom. (305347@main) (150976146)<col> elements with span > 1 not applying their width to all spanned columns during table layout, aligning behavior with other browsers. (305113@main) (167225435)min-width distribution for spanning cells with mixed percent, fixed, and auto columns. (305215@main) (167684748)<clipPath> to clip to its <use> child element based on the visibility of the <use> target element. (305374@main) (167491519)<text> and <tspan> elements in SVG. (305221@main) (167691166)DeviceMotionEvent and DeviceOrientationEvent interfaces so that they only show up in secure contexts just like the corresponding events and made ondevicemotion and ondeviceorientation enumerable, aligning with the specification. (305266@main) (44804273)DigitalCredential protocols by gracefully filtering them out and showing a console warning instead of throwing an error. (305257@main) (166673454)RTCConfiguration.iceServers to be a non-optional sequence with an empty array as the default, improving spec compliance and ensuring RTCPeerConnection behaves correctly when iceServers is undefined. (305152@main) (167607478)Currently, the finalized syntax for Grid Lanes is available in Safari Technology Preview. Edge, Chrome and Firefox have all made significant progress on their implementations, so it’s going to arrive sooner than you think.
Plus, you can start using it as soon as you want with progressive enhancement. This article will show you how.

(If you haven’t heard of Grid Lanes yet, it’s a new tool for layout that makes it easy to create masonry-style layouts in CSS alone. Read Introducing CSS Grid Lanes to learn all about it. And read New Safari developer tools provide insight into CSS Grid Lanes to learn about our new developer tooling that makes using Grid Lanes it even easier.)
Where are browsers in the process of getting ready to ship support for Grid Lanes? Let’s look at the progress that’s been made over the last seven years.
It’s the team that was at Mozilla in 2019-2020 who wrote the original CSS Working Group Editor’s Draft for Grid level 3, proposing concrete ideas for how masonry-style layouts would work in CSS. The feature shipped in Firefox Nightly in very early 2020. Some of the syntax has since changed, but under the hood, the way this new layout feature relies on and expands CSS Grid is basically the same, which means much of the heavy lifting for implementing it in the Gecko layout engine is underway.
Firefox does need to update their implementation (including updating to the new syntax and adding the new flow-tolerance property, among other things) but if you want to try it out in Firefox today, you can enter about:config in the URL bar, search for “masonry” and set the flag to true — or use Firefox Nightly where it’s already on by default. (At the moment, remember to use the original grid-template-*: masonry syntax to trigger this layout, instead of display: grid-lanes.)
In 2022, Safari’s WebKit team picked up where Mozilla left off in 2020, and started implementing the same original proposal for CSS Grid Layout Level 3. We also restarted the discussion inside the CSS Working Group, hoping to advance the original Editor’s Draft to a point where it was mature enough that browsers could feel confident shipping.
The WebKit implementation was enabled on-by-default in Safari Technology Preview 163 in February 2023. It’s been updated continuously as the CSS specification has changed.
You can use Safari Technology Preview today to try out the official web standard, make demos using display: grid-lanes, and learn how it works. Keep an eye on the Safari Release notes to see when it ships in Safari beta.

A variation for how masonry layouts could work in CSS landed in Chrome and Edge 140 behind a flag in July 2025. Rather than implementing the same syntax as Safari and Firefox, Chromium experimented with an alternative proposal. This drove debates in the CSSWG about how exactly this feature should work and what its syntax should be. With key syntax decisions now finalized, Chromium engineers at Edge are updating their implementation. Keep an eye on the Chrome Status issue for the latest news.
Bottom line — all the major browser engines are making progress. Now is a great time to learn how Grid Lanes works. And consider if, when and how you could start using it.
Great developers are always mindful of users whose browsers don’t have support. Not only does it take time for all browsers to ship new features, it takes time for all users to update. But this does not mean you have to wait multiple years before using new technologies. It just means you just have to be savvy.
You can progressively enhance your code to create masonry-style layouts, and support older browsers, both at the same time. How? As always, there are multiple options. Which choice is best for you depends on your use case, your team, and your code base. Let’s go through three different approaches you could use:
One common trick for using a new CSS feature when it’s still not available in all browsers is to use a polyfill — i.e.: use JavaScript to fill in the missing functionality.
Lucky for you, there are already tried and true JS libraries out in the world for creating masonry layouts. Masonry.js is a popular one. Perhaps you are using it now. You can keep using it by itself, and ignore Grid Lanes. Or you can switch to using the JS library as a polyfill.
The approach here is to go ahead and use CSS Grid Lanes to handle the layout in CSS alone — in browsers with support for Grid Lanes, even if that’s still only preview browsers. At the same time, architect your code to also work with a JavaScript library. Test in a browser without support for Grid Lanes to make sure the JS layout works.
The key is to structure your code with conditionals, so browsers with Grid Lanes support use CSS, while those without use JS. In your CSS, use Feature Queries to ensure the right CSS is used under the right conditions. In JavaScript, use if statements.
For example, you can structure your CSS like this:
/* Native Grid Lanes for supporting browsers */
@supports (display: grid-lanes) {
.grid {
display: grid-lanes;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1lh;
}
}
/* Additional CSS needed only for browsers without Grid Lanes */
@supports not (display: grid-lanes) {
.grid-item {
margin: 1lh;
}
/* Perhaps also include a fallback layout in case JS doesn't run */
}
Then in JavaScript, you can check to see whether or not Grid Lanes is supported. If not, load the file. And then start using Masonry JS (or another library), according to its documentation.
// Check if CSS Grid Lanes is NOT supported
if (!CSS.supports('display', 'grid-lanes')) {
// Dynamically load masonry.js
const script = document.createElement('script');
script.src = 'https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js';
script.onload = function() {
// Use Masonry.js after the script loads
new Masonry('.grid', {
itemSelector: '.grid-item',
columnWidth: 200,
});
};
document.head.appendChild(script);
}
It’s important to conditionally load the JS library only if the browser doesn’t support Grid Lanes. There’s no reason to have all users download and run the JS file when some percent don’t need it. That percentage might be small today (even zero), but over time it will grow to 100%.
Save future you the task of having to change your code later. Structure it today so in a future when all users have Grid Lanes, no one has to do anything. Users get the best experience, even if no one on your team ever cleans out the old code.
With this technique, browsers with Grid Lanes support use pure CSS, while older browsers load and use JavaScript. By switching to using the JavaScript library a polyfill, not as the primary layout mechanism, increasing numbers of users will get the benefit of a faster and more robust layout sooner.
Of course, maybe this won’t work for your project. Maybe it’s too complicated to architect your HTML and surrounding layout to work for both Grid Lanes and a masonry library at the same time. So what are the other options?
Of course, you might be screaming “it’s too early to use Grid Lanes!” There is always the option of simply waiting to use a new technology. Perhaps another layout mode in CSS like Grid level 1, Flexbox or Multicolumn are good enough for your needs. And you can hold off using any tool for accomplishing a masonry-style layout until you feel more confident about Grid Lanes.
CSS Multicolumn is an interesting option that you might not be familiar with. It shipped in browsers decades ago (before Can I Use kept track). With origins that date back to the 1990s, Multicolumn suffered from the fate of most early CSS — the specification was not detailed enough, and that resulted in a lot of differences between browser implementations. This frustrated developers, resulting in Multicolumn falling out of favor.
In more recent years, Multicolumn level 1 has gotten a lot of love, and the specification now contains far more detail. This has helped browsers squash interop bugs. There’s even a Multicolumn level 2 specification bringing new features in the future. There’s still more work to do to create true interoperability, but it’s worth reconsidering Multicolumn to see if can solve your use case today.
Multicolumn and Grid Lanes can result in very similar-looking layouts. They are fundamentally different, however, in the way content flows. These differences impact the order of what comes into focus when tabbing through content, readability / scanability, and user experience. So consider carefully.

Try out the demos we created to compare how Multicolumn and Grid Lanes work. Select different layouts from the dropdown menus, and turn on item numbers to emphasize the difference.
While “don’t use Grid Lanes” is always an option, perhaps the best approach is to write your code so that Grid Lanes is used when supported, and another layout mode in CSS is used as the fallback. This avoids using JavaScript for layout, while still delivering the newer layout to the users who do have support.
For example, let’s imagine we want to use Grid Lanes, and leverage CSS Grid (level 1) when Grid Lanes isn’t supported. To make the original Grid layout work, can use CSS to force all the items be the same aspect ratio by cropping images and truncating text.
To do this, we can apply layout to the container using the display property — twice. First we’ll declare display: grid, then we’ll immediately declare display: grid-lanes.
.grid-container {
display: grid;
display: grid-lanes; /* will override grid in browsers that support */
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1lh;
}
In browsers that support Grid Lanes, the second declaration will override the first. The display: grid rule will be ignored. And the layout will use Grid Lanes, resulting in a layout that packs content of different aspect ratios into a set of columns.
In browsers that do not support Grid Lanes, the browser will ignore the second declaration. It sees display: grid-lanes and goes “what? That’s not a thing. You must have misspelled something. Ignore!” This leaves grid as the layout that’s applied. The content will be laid out into clear rows as well as columns.
This is a tried and true technique that’s been used by developers for over two decades — relying on the fact that CSS just ignores anything it doesn’t understand. It does not throw an error message. It does not stop parsing. It just ignores that line of code and moves along.
We can also use a Feature Query to write code that only gets applied in browsers without support for Grid Lanes. Let’s use the aspect-ratio property to force all images into the same aspect ratio. And use object-fit: cover to crop those images to fit in the box, instead of letting them be squished.
/* Additional CSS for browsers without Grid Lanes support */
@supports not (display: grid-lanes) {
.grid-item {
img {
aspect-ratio: 1; /* resize every image into a square */
object-fit: cover; /* crop, don't squish the image */
}
h3 {
white-space: nowrap; /* don't wrap the headline */
overflow: hidden; /* crop the extra */
text-overflow: ellipsis; /* add an ellipsis if it overflows */
}
p {
display: -webkit-box; /* current practice in all browsers */
-webkit-box-orient: vertical;
-webkit-line-clamp: 3; /* clamps to this many lines of text */
overflow: hidden;
}
}
}
We can force our headline to not wrap with white-space: nowrap. Once the headline is on one line, we can hide whatever doesn’t fit with overflow: hidden. Then, text-overflow: ellipsis adds “…” to the end of what’s visible.
When we want to truncate multi-line text to a specific number of lines, we can use the -webkit-line-clamp technique. While originally invented by the WebKit team long ago when prefixes were the best practice for browsers rolling out new ideas, today -webkit-box, -webkit-box-orient and -webkit-line-clamp are supported by all browsers. (No browser has shipped a replacement yet because a complete web standard for defining such a tool is still under debate.)

This approach results in a masonry-style waterfall layout being delivered to the browsers that support Grid Lanes, while a more traditional layout of equal-sized boxes are delivered using CSS Grid level 1 to browsers that don’t yet support Grid Lanes.
It’s totally up to you how you want to handle the fallback for a lack of support for Grid Lanes, but you definitely have options. This is one of the benefits of writing CSS, and not just using a 3rd-party utility framework that abstracts all the flexibility away. Progressive enhancement techniques bring the future into the present, and let you start using Grid Lanes far sooner!
This is our third article in a series about Grid Lanes. The first introduces CSS Grid Lanes, explaining what it is and how to use it (and yes, it can be used “in the other direction”). The second article shows off our new developer tools and explains why they are particularly helpful for setting flow tolerance. Also, check out our demos in Safari Technology Preview. And be sure to come back to webkit.org soon for more articles about Grid Lanes.
]]>
CSS Grid Lanes adds a whole new capability to CSS Grid. It lets you line up content in either columns or rows — and not both.
This layout pattern allows content of various aspect ratios to pack together. No longer do you need to truncate content artificially to make it fit. Plus, the content that’s earlier in the HTML gets grouped together towards the start of the container. If new items get lazy loaded, they appear at the end without reshuffling what’s already on screen.
It can be tricky to understand the content flow pattern as you are learning Grid Lanes. The content is not flowing down the first column to the very bottom of the container, and then back up to the top of the second column. (If you want that pattern, use CSS Multicolumn or Flexbox.)
With Grid Lanes, the content flows perpendicular to the layout shape you created. When you define columns, the content flows back and forth across those columns, just like to how it would if rows existed. If you define rows, the content will flow up and down through the rows — in the column direction, as if columns were there.

Having a way to see the order of items can make it easier to understand this content flow. Introducing the CSS Grid Lanes Inspector in Safari. It’s just the regular Grid Inspector, now with more features.

Safari’s Grid Inspector already reveals the grid lines for Grid Lanes, and labels track sizes, line numbers, line names, and area names. Now it has a new feature — “Order Numbers”.
By turning on the order numbers in the example above, we can clearly see how Item 1, 2, 3, and 4 flow across the columns, as if there were a row. Then Item 5 is in the middle right, followed by Item 6 on the far right, and so on.
You might be tempted to believe the content order doesn’t matter. With pages like this photo gallery — most users will have no idea how the photos are ordered in the HTML. But for many users, the content order has a big impact on their experience. You should always consider what it’s like to tab through content — watching one item after another sequentially come into focus. Consider what it’s like to listen to the site through a screenreader while navigating by touch or keyboard. With Grid Lanes, you can adjust flow-tolerance to reduce the jumping around and put items where people expect.
To know which value for flow tolerance to choose, it really helps to quickly see the order of items. That makes it immediately clear how your CSS impacts the result.
Order Numbers in the Grid Inspector is an extension of a feature Safari’s Flexbox Inspector has had since Safari 16.0 — marking the order of Flex items. Seeing content order is also helpful when using the order property in Flexbox.

Order Numbers in Safari’s Grid Inspector works for CSS Grid and Subgrid, as well as Grid Lanes.
The Grid and Flexbox layout inspectors might seem similar across browsers, but the team behind Safari’s Web Inspector has taken the time to finely polish the details. In both the Grid and Flexbox Inspectors, you can simultaneously activate as many overlays as you want. No limits. And no janky scrolling due to performance struggles.
Safari’s Flexbox Inspector visually distinguishes between excess free space and Flex gaps, since knowing which is which can solve confusion. It shows the boundaries of items, revealing how they are distributed both on the main axis and the cross axis of Flexbox containers. And it lists all the Flexbox containers, making it easier to understand what’s happening overall.
Our Grid Inspector has a simple and clear interface, making it easy to understand the options. It lists all of the Grid containers — and you can show the overlay for every single one at the same time. The overlays don’t disappear when you scroll. And of course, you can change the default colors of the overlays, to best contrast with your site content.
And Safari’s Grid and Flexbox Inspectors are the only browser devtools that label content order. We hope seeing the order of content in Grid Lanes helps you understand it more thoroughly and enjoy using this powerful new layout mechanism.
Order Numbers in Safari’s Grid Inspector shipped today in Safari Technology Preview 235. Let us know what you think. There’s still time to polish the details to make the most helpful tool possible. You can ping Jen Simmons on Bluesky or Mastodon with links, comments and ideas.
This release includes WebKit changes between: 304072@main…305083@main.
@container queries that have no conditions, allowing named containers to match without explicit constraints. (304388@main) (164648718)math-depth. (305010@main) (167332590)display: list-item was incorrectly supported on fieldset. (304102@main) (95638460)-webkit-line-clamp so that it no longer propagates into inline-block children. (304956@main) (164488778)filter effects were not rendered. (304143@main) (165163823)::first-line pseudo-element to always use inline display to match the CSS Display specification. (304096@main) (166068698)FontFaceSet constructor from the CSS Font Loading API as it was deemed unnecessary, aligning with the CSSWG resolution. (304912@main) (132031306)ImageBitmap created from SVG image sources to correctly honor the flipY orientation. (304137@main) (83959718)TextDecoder streaming decoder to properly manage partial sequence buffers. (304496@main) (166583808)HTMLImageElement.currentSrc to return an empty string for <img src="proxy.php?url="> instead of resolving to the document base URL. (304982@main) (167229274)%TypedArray%.prototype.includes to correctly check that the index is less than the array length, aligning its behavior with ECMA-262 (304485@main). (166578170)%TypedArray%.prototype.includes to correctly check that the index is less than the array length, aligning its behavior with ECMA-262. (304940@main) (167183441)contain-intrinsic-inline-size and contain-intrinsic-block-size. (304458@main) (166323213)math-style and math-shift to animate as discrete values. (305017@main) (167369164)mpadded content in right-to-left mode. (304087@main) (166045517)RenderMathMLRoot did not reset its radical operator when msqrt or mroot children were dynamically added or removed.(304822@main) (166556627)<mpadded> so that percentage values for width, height, and depth attributes are treated as absent and use content dimensions as defaults, matching the MathML Core specification. (305027@main) (167350169)HTMLMediaElement volume from 0 to 0 did not activate the audio session or update the sleep disabler. (304297@main) (161691743)application/ogg blob media. (304530@main) (163119790)HTMLMediaElement did not correctly detect new audio or video tracks causing Safari to pause video when leaving a tab. (304336@main) (164514685)AudioData.copyTo() when copying the last channel of 3-channel audio. (304728@main) (164730320)On option to correctly enable the highest-scoring text track and mark the appropriate language as checked in the subtitle menu. (304462@main) (166158394)parseSequenceHeaderOBU to return an AV1CodecConfigurationRecord, fully decode the Sequence Header OBU, and capture the complete color profile. (304474@main) (166439682)fetch() would throw a TypeError when using targetAddressSpace: 'loopback' for localhost requests. (304577@main) (166574523)list-style-type by ensuring outside list markers do not affect intrinsic width calculations. (304947@main) (164650313)getClientRects returned an incomplete list of rectangles for inline boxes containing block elements. (304949@main) (167209147)lighter operator in SVGFECompositeElement IDL to align with the Compositing and Blending specification. (304941@main) (166704079)stroke-dasharray incorrectly propagated to SVG markers when explicitly marked as ‘0’. (305042@main) (46607685)foreignObject elements in SVG incorrectly allowed margin collapsing. (304916@main) (97208795)marker-start, marker-mid, or marker-end attributes on SVG elements did not trigger re-rendering. (304880@main) (130678384)<feDisplacementMap>. (304830@main) (135448018)animateTransform animations on hidden elements were triggering full-page rendering updates each frame. (304744@main) (159647563)SVGLength percentage resolution for elements inside non-instanced <symbol> elements. (304197@main) (165431008)SVGLength.value did not update for font-relative units (e.g., ch, em) after changes to writing-mode. (304657@main) (166190252)stroke fill. (288788@main) (166997630)<svg> elements in <img> without an explicit viewBox to synthesize preserveAspectRatio='none' so the SVG stretches to fill the container. (305043@main) (167121931)<stop> element offset attribute in SVG to reject invalid values with trailing characters and correctly fall back to 0. (305036@main) (167356988)clipPath elements so that the bounding box is scaled correctly before applying the local transform. (305078@main) (167417135)markerUnits=strokeWidth with vector-effect=non-scaling-stroke. (305077@main) (167493417)IntersectionObserver computed the root rectangle incorrectly when overflow clipping was present. (304296@main) (117143395)DigitalCredential behavior to make user mediation implicitly required. (304676@main) (165597827)TextDecoder failed to recover and emit ASCII characters after encountering an invalid leading byte. (304591@main) (166672674)Order Numbers in CSS Grid and CSS Grid Lanes overlays in Web Inspector, including new UI settings for toggling order number visibility. (304645@main) (166648769)grid-lanes layouts within the Web Inspector grid overlay. (304861@main) (166984079)Copy HTTP Request and Copy HTTP Response to Copy HTTP Request Headers and Copy HTTP Response Headers for clarity. (304768@main) (117708766).0 values for readability. (304851@main) (166500013)# symbol, changing from Item #N to Item N. (304771@main) (166767949)Error.isError(WebAssembly.Exception) to correctly return false based on current WebAssembly spec semantics. (304917@main) (167110254)RTCDataChannelInit to support [EnforceRange] on the maxPacketLifeTime and maxRetransmits fields to align with the WebRTC specification. (304499@main) (133630397)RTCDataChannel close events did not fire when RTCPeerConnection was closed. (304859@main) (165617848)