Compared to other modern web browsers, Safari is a bit slower in rolling out feature updates, possibly due to Apple’s cautious approach or reliance on the slower-evolving Webkit rendering engine. Since these features are not user-facing and mainly concern developers, Safari’s slow-paced implementation of new features and updates can largely impact the overall DX.
Although skipping Safari to deploy apps faster may sound smart from a developer’s point of view, it may backfire since Safari holds a significant position in the Apple ecosystem and has a sizeable market share.
The good news is that Safari has been continuously improving and rolling out new features more frequently this year. The Safari 17.4 update brought in almost 46 modern features and over 145 bug fixes, followed swiftly by the 17.5 update with significantly fewer development-centric updates.
This article highlights all the major development-specific updates in Safari 17.4 that you, as a web developer, should be aware of. Let’s dive right in.
Safari 15.4 and subsequent releases have introduced and maintained important CSS features, like cascade layers, container queries, the :has()
pseudo-class, and the subgrid.
With Webkit 17.4, Safari continues to improve the DX by adding support for additional CSS features that were previously expected but unavailable. Below, you’ll find a breakdown of some of the major CSS features and updates you will experience with Safari 17.4.
Style scoping simplifies CSS specificity and overriding by allowing the creation of style boundaries. It provides the @scope
rule and the :scope
selector to target DOM subtree elements with loosely coupled selections.
Keep in mind that style scoping is still an under-developed feature. However, considering its benefits, some of the major browsers have already started providing support for style scoping, including Safari 17.4.
You can now use the @scope
at-rule in Safari to apply scoped styles to a particular selector and encapsulate the styles within it. The below code covers an example of how scoped styles simplify working with different component patterns that have similar elements while keeping the styles encapsulated and less specific:
/* Styles that can easily leak out and affect other components */ .toast { ... } .toast-title { ... } .toast-cta { ... } /* A better way to encapsulate styles with scoped styles */ @scope .toast { :scope { ... } .toast-title { ... } .toast-cta { ... } }
For a more illustrated explanation, you should explore and compare the following demos. This first CodePen demonstrates the leaking styles problem:
See the Pen A demo of CSS styles leaking out by Rahul C (@_rahul)
on CodePen.
Meanwhile, here is its solution using scoped styles:
See the Pen Style leaking fixed with @scope rule by Rahul C (@_rahul)
on CodePen.
In the future, this approach is expected to help developers create easily maintainable and reusable components without relying heavily on naming methodologies like BEM.
:has()
pseudo-classOne of the best additions in CSS Selectors Level 4 was the :has()
pseudo-class, which allows you to select the parents inside-out based on their children. The :has()
pseudo-class is now widely supported in all modern browsers, including Safari 15.3 and beyond.
It’s now a bit more polished in Safari 17.4. You can now use :link
and :any-link
pseudo-classes as an argument to :has()
to specify the criteria of an element having a link of a certain nature, which was not possible in the earlier versions:
.card:has(:link) { cursor: pointer; border-color: var(--color-link-unvisited); ... } .card:has(:any-link) { cursor: pointer; border-color: var(--color-link-general); ... }
But there’s a catch. The :any-link
pseudo-class is still pretty new and has varying levels of support across different browsers. Its sole purpose is to select links regardless of their visited state. Since it’s still in its experimental phases, it might not perform as expected.
The inconsistent browser support for :link
and :any-link
makes their use with :has()
unreliable, which can result in :link
selecting everything and :any-link
and :visited
not selecting anything at all. As a result, you should use them with caution.
Safari now supports vertical writing mode for form controls. This mode allows text boxes, buttons, sliders, progress bars and other form elements to be displayed vertically, which is particularly useful for CJK scripts and languages.
Vertical writing mode supports elements such as button
, textarea
, progress
meters, input
, and <select>
. Here’s an example of how to use it:
[lang="zh-CN"], [lang="zh-TW"], [lang="ja"], [lang="ko"] { .wm-vertical-rl { writing-mode: vertical-rl; text-orientation: upright; } }
The result might look something like the following:
See the Pen Demonstrating vertical-rl writing-mode by Rahul C (@_rahul)
on CodePen.
It’s worth noting that the <select>
box popup will always be displayed the same way, regardless of its writing mode, as it is normally displayed. If you are working with vertical mode, you might also find it helpful to read about vb and vi viewport units.
Another interesting addition to modern-day CSS is to be able to align the element’s content without having to be concerned much about its display.
The align-content
property now works out of the box to align the content vertically within its block-level or table-level parent. Currently, block-level alignment is well-supported on Chrome and Firefox, while Safari 17.4 fully supports the alignment in both block and table formatting contexts.
Here’s an example usage of the align-content
property:
See the Pen Using `align-content` prop in block-level elements by Rahul C (@_rahul)
on CodePen.
The traditional white-space
property in CSS determines and controls two tasks: collapsing whitespaces and wrapping lines. In the ever-evolving world of CSS, these tasks are expected to have separate properties for better context and more granular control.
There are now two separate properties to control each of these tasks — white-space-collapse
and text-wrap-mode
— which are also pretty much self-explanatory in what they do:
.wrapped-text { width: 200px; /* Set a fixed width to force wrapping */ border: 1px solid #ccc; /* Add a border for visual clarity */ white-space-collapse: break-spaces; /* Collapse multiple spaces into one */ text-wrap-mode: break-word; /* Allow breaking words in the middle */ }
Note that the text-wrap-mode
property currently lacks support in Chromium-based browsers. Also, the letter-spacing
and word-spacing
props on Safari can now have percentage values relative to the font size, which is a nice addition when it comes to typography.
From CSS4 onwards, the developers can enable transitions for discrete CSS properties like display
and content-visibility
using the allow-discrete
transition behavior.
The feature is new to Safari and has yet to settle down and become stable. It has great potential to redefine the general interaction design in the browser without needing JavaScript for interactivity.
Here’s an example of how to use it:
.card { transition: opacity 0.25s, display 0.25s; transition-behavior: allow-discrete; } .card.fade-out { opacity: 0; display: none; }
However, keep in mind that Safari still does not support display
and some other major discrete properties through the allow-discrete
behavior of CSS transitions.
Check out this example of list-style
, another discrete CSS property, using the allow-discrete
transition behavior and changing values after the delay added by the transition duration:
See the Pen list-style with allow-discrete transition behavior by Rahul C (@_rahul)
on CodePen.
::backdrop
Safari is perhaps the first browser that enables you to apply CSS variables or custom properties to the ::backdrop
pseudo-element. Note that ::backdrop
is only supported by certain special elements like dialogs, popovers, or anything that enters full-screen using the FullScreen API.
Again, you need to use this feature mindfully, as it’s unavailable with other major browsers:
:root { --dialog-backdrop: hsl(0deg 20% 30% / 10%); } dialog::backdrop { background-color: var(--dialog-backdrop); }
The new ::grammar-error
and ::spelling-error
pseudo-elements allow the developers to decorate spelling and grammar errors in user-inputted text if the browser provides the spell-check facility.
Although previously unsupported, these under-developed features are now available for you to play with in Safari 17.4. You can highlight spelling and grammatical errors in text added through HTML text-field
, textarea
, and contenteditable
elements and attributes. For example:
::grammar-error { text-decoration: wavy; color: orange; } ::spelling-error { text-decoration: wavy; color: red; }
Here’s the result:
Some browser features, such as source prioritization of media, are often delayed due to the browser’s inability to provide the necessary support and APIs. The recent Safari update has improved its capabilities, which have affected the usage, functionality, and usefulness of the following HTML features.
The native checkbox switch looks like an unconventional feature at the moment, as it’s not at all supported anywhere other than Safari. It’s said that support for the native switch is expected everywhere soon, as the switch pattern is becoming more common in frontend design systems.
You can implement this native switch in Safari just by adding a switch
attribute to the checkbox input in your web pages as shown below:
<input type="checkbox" switch>
Here’s the result:
The native switch also allows you to customize itself using the ::track
and ::thumb
pseudo-elements. Furthermore, unlike the CSS trickery we do normally to emulate the checkbox switch, we can use the labels separately which is pretty neat.
<select>
elementsNormally, you’ll find using horizontal rules inside the <select>
element a bit unconventional, even though modern browsers actually allow you to do so. Modern browsers support this behavior to improve the way option groups are grouped and separated, which also helps users read and scan them efficiently.
Here’s a simple example of this behavior, which should work well in Chrome 119+, Firefox 122+ and Safari 17.4+:
See the Pen Horizontal Rules inside Select Inputs by Rahul C (@_rahul)
on CodePen.
However, this behavior is not supported universally yet. You should rely on the traditional approach by adding borders as a fallback to optgroups
to show the distinction and spacing between the <select>
element’s options.
Safari added media extension support for WebCodecs HEVC and VP8/VP9 video, along with Vorbis audio codec support for iOS and iPadOS. With this support for additional media formats, Safari now also allows you to prioritize video sources directly through your HTML markup.
For instance, in the code below, the browser will go through the list of sources and choose the first source it can play based on the video format support and the media query provided. It will keep moving down the list to find a playable source; if it doesn’t, it will show just a fallback message at the end:
<video> <source src="video_high.mp4" type="video/mp4" media="(min-width: 1200px)"> <source src="video_high.webm" type="video/webm" media="(min-width: 1200px)"> <source src="video_medium.mp4" type="video/mp4" media="(min-width: 768px)"> <source src="video_medium.webm" type="video/webm" media="(min-width: 768px)"> <source src="video_low.mp4" type="video/mp4"> <source src="video_low.webm" type="video/webm"> <p>The video cannot be played due to either an unsupported video format or a network issue.</p> </video>
There are a number of bug fixes and new introductions in JavaScript features and different JavaScript APIs in Safari 17.4. Let’s go through some of the major ones.
Webkit has improved how effectively its JavaScript ArrayBuffer API manages the buffer ownership. It did so by adding two new methods for enhanced responsibility: transfer
and transferToFixedLength
, both of which are ready to be used in Safari 17.4.
Previously, multiple variables referencing an ArrayBuffer
object could lead to unwanted behavior. The new ownership concept ensures a single owner using these new transfer methods, which is as simple as shown in the code below:
const originalBuffer = new ArrayBugger(1024); const newBuffer = originalBuffer.transfer(); console.log(originalBuffer.byteLength); // => 0 console.log(newBuffer.byteLength); // => 1024 console.log(originalBuffer.detached); // => true
AbortSignal
composition with the any
methodIn modern JavaScript, the AbortSignal
interface serves as a communication channel between the code initiating an operation and the operation itself. It allows for a graceful cancellation of async operations such as network requests, timeouts, and more.
The any
method is a new feature of the AbortSignal
interface that accepts multiple AbortSignals
as an array and acts as a combined signal for all these inputs. It returns a single AbortSignal
object that gets triggered when any of its input signals are aborted:
const controller1 = new AbortController(); const controller2 = new AbortController(); const combinedSignal = AbortSignal.any([ controller1.signal, controller2.signal, AbortSignal.timeout(5000) // Abort after 5 seconds ]); fetch('https://api.example.com/data', { signal: combinedSignal }) .then(response => { /* ... handle response */ }) .catch(error => { if (error.name === 'AbortError') { // Handle the abort (could be from timeout, manual cancellation, etc.) } else { // Handle other errors } }); // Later, you can abort based on any of the conditions: // controller1.abort(); // controller2.abort(); // Or the timeout will trigger after 5 seconds
withResolvers
methodTraditionally, creating and resolving promises involved defining an executor function within the Promise
constructor. However, this is a bit of a messy practice now.
To simplify the creation of promises with explicit resolve
and reject
handlers, you can now use the Promise.withResolvers
method to return an object containing a new promise
object, along with functions that you can call to resolve
and reject
the promise
from anywhere in your code:
const { promise, resolve, reject } = Promise.withResolvers(); /* * Perform some async operation * ... */ /* * Resolve or reject the promise * based on the operation outcome */ if (...) { resolve(...); } else { reject(...); }
checkVisibility
methodThe checkVisibility
method is a general Web API feature in modern browsers. This method categorizes an element as visible or not visible in the viewport by considering multiple CSS factors like opacity
, visibility
, content-visibility
, display
, and more.
With Webkit 17.4, Safari now provides a bit of partial support to this method where you may use the checkOpacity
and checkVisibilty
parameters to determine specific property values. However, the checkContentVisibility
parameter is not yet supported.
Here’s an example of how to use this method:
const someElement = document.getElementById('some-element'); const isVisible = someElement.checkVisibility(); if (isVisible) { console.log('The element is visible!'); } else { console.log('The element is not visible.'); }
The Object.groupBy()
method gets full support in Safari 17.4 which makes it easy to group datasets as can be seen below:
// Sample data const products = [ { name: "Laptop", category: "Electronics" }, { name: "T-shirt", category: "Clothing" }, { name: "Smartphone", category: "Electronics" }, { name: "Jeans", category: "Clothing" }, { name: "Headphones", category: "Electronics" } ]; // Grouping by category const groupedProducts = Object.groupBy( products, (product) => product.category ); console.log(groupedProducts);
You should check out this demo and observe the inputs along with the corresponding output in the developer console to see Object.groupBy
in action.
A similar method, Map.groupBy
, is also now available in Safari. This method returns a Map
object instead.
setHTMLUnsafe()
methodThe setHTMLUnsafe
method is a new way to completely replace the contents of an element, shadow root, or document with a provided HTML string.
const htmlElement = document.getElementById('htmlElement'); htmlElement.setHTMLUnsafe('<span>Injected markup</span>');
The use of “unsafe” in the method implies one potential risk to use this method: it doesn’t sanitize the HTML it injects, which — if not managed carefully — could make it vulnerable to XSS attacks.
With all the hard work you put into your websites and web apps, it’s critical to ensure that all users can view and interact with your web content. Let’s discuss some of the accessibility enhancements in Safari 17.4.
The new Safari update now avoids unnecessary announcements for certain ARIA landmark roles when navigating to landmarks using the rotor.
It consistently announces the focus of form elements with the VoiceOver screen reader and provides additional information about form controls such as checked checkboxes and radios. The live region ARIA elements now support announcements, making real-time updates more accessible.
Developers often use Unicode characters or images in ::before
, ::after
, and ::marker
pseudo-elements for different purposes. By default, these don’t convey anything about the element itself.
To ensure that elements with dynamically generated content are accessible to screen readers and other assistive technologies, you can add alternative generated content using the content
property directly:
.error-icon::before { content: url("error.svg") \ "Error occured: "; }
This significant accessibility enhancement is now supported by all major browsers, including Safari 17.4, and is expected to receive some semantic sugar in future updates.
As developers, we’re constantly looking for ways to improve performance, much of which relies on the browser’s capabilities. The Safari 17.4 release supports this with a few enhancements, which we’ll go through below.
Safari claims numerous architectural improvements have resulted in a noticeable performance boost in version 17.4 compared to previous versions. We have to wait for some developer feedback on JavaScript performance in the new Safari, as there are no JavaScript execution benchmarks available currently.
WebGL in Safari now features improved Multisample Anti-Aliasing (MSAA) rendering, which results in smoother 3D graphics.
Additionally, nested workers are enabled for flexible multi-threading. Some new extensions have also been added for better clip control, different polygon modes, additional polygon offsetting control, and prevention of primitive clipping based on depth.
The Web Inspector in the new Safari has been updated with a bunch of crucial improvements, including:
Other enhancements likely include various bug fixes, performance optimizations, and potential UI tweaks for a smoother debugging experience.
Web browsers can help make the lives of developers easier by continuously adding new features that allow them to focus more on utilizing the actual features rather than using polyfills.
At the time of writing this article, Safari introduced the 17.5 technical preview, released it as a stable version, and then introduced the 17.6 technical preview. This reflects the frequent updates and rapid development by Webkit developers to improve the browser engine and Safari as a whole.
Do you consider Safari feature support when working on projects? How do these recent updates in Safari affect your workflow? Share with us in the comments section.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowAutomate code comments using VS Code, Ollama, and Node.js.
Learn to build scalable micro-frontend applications using React, discussing their advantages over monolithic frontend applications.
Build a fully functional, real-time chat application using Laravel Reverb’s backend and Vue’s reactive frontend.
console.time is not a function
errorExplore the two variants of the `console.time is not a function` error, their possible causes, and how to debug.