Understanding Safari - Browser Limitations and How to Overcome Them

(Updated on )

A meme comparing the Internet Explorer with Safari by using the 'how it started' versus 'how is it going' texts

There is nothing new under the sun when we say Safari is the new Internet Explorer. This comparison is made as Safari represents a important share of the market size while having a subpar experience compared to its competitors, similar to what happened with Internet Explorer in the 2010s.

While I don’t think every browser should be a fork of Chromium and that WebKit and Gecko engines contributes to a heterogenous ecosystem, I believe the type of browser should be at least something the we must have freedom to choose.

It is also important to mention and recognize the work of partners at Igalia and the individuals from Web Interoperability at Apple do to improve the WebKit engine.

With that said, I’ve encountered a series of issues or different behaviors that happens only in Safari and therefore this article is aimed to be a reference of them. Is there anything I missed or do you have any experience worth sharing? Please send me a message or a PR.

I hope this article gets outdated soon.

Issues

Vendor lock-in

You can’t troubleshoot Safari specific problems if you don’t have a MacBook. Simple as that. There is no way to install Safari on a Windows or Linux machine. For those, you need to use either WebKitGTK or work around it using the Playwright CLI.

Now for Safari iOS, besides the MacBook you will need also an iPhone device if you want to troubleshoot and access the Developer Tools. Otherwise, you will need to rely into 3rd party tools like BrowserStack that lets you use a real device within your browser for a few minutes.

CSS Filters

Any element using a CSS filter would not render in Safari unless you add a translate3d(0, 0, 0.01) along with the filter you want to apply.

Images being downloaded multiple times

Note: Most of frameworks already handle this, like Next.js caught in 2021.

There is a bug in Safari in image tag definition using the combination of srcset and sizes. If the sizes attribute appears after srcset, it will download them all and disregard the sizes attribute altogether.

Slider components

As originally reported in material-ui and react-range GitHub repositories.

In Slider components like the MUI one, there is a bug for Safari mobile browser in onChange event listener where it fires twice. We literally need to check if is IOS and prevent that manually:

const isIOS = () => {
	// function here that checks the user agent for possible iOS devices
}

const handleChange = (event, newValue) => {
	if (isIOS() && event.type === 'mousedown') {
		return
	}
	// Otherwise handle your change event as normal
}

Multiple Audio Streams

Safari allows only one stream of audio to be played each time. In order to do play multiples synchronously, you need to control the muted attribute of both, starting them with a truthy value and then toggling it whenever you want to play or pause a stream. In the example below, it works but you can notice the workaround in the JS code.

Worth also to mention this other solution by Matt Harrison that uses Web Audio API.

Flex containers with text-decoration-thickness

In components like Navbar where we want to add a thicker underline below the links, we usually do so by adding the text-decoration-thickness property to the element. However, it doesn’t get set by Safari browser if that same element is a flex container.

Removing the flex property from the element fixes the issue. As a workadound, you can also fix it by adding a pseudoselector that draws that same underline manually.

Missing features

Network Information API

Note: It does not work on Firefox either.

This API acessible from navigator.connection provide useful information about the current connection from the user perspective and can be used to deliver a tailored user experience or optimize SEO meterics. For exampple, you can check if they are connected to your website using a slow connection and therefore serve a lower quality image.

URLPattern matching API

Note: It does not work on Firefox either.

This API is intended to work like Node Express Router does like in:

app.get('/users/:userId/books/:bookId', (req, res) => {
	res.send(req.params) // expect userId and bookId to be defined
})

with the goal to match specific patterns in the URL. This would save a lot of shady code that needs to execute like const book = url.split('/')[3] to get the value of a specific slice of the URL you are looking for.

CSS Anchor positioning

Note: It does not work on Firefox either.

This CSS feature enables us to declaratively position elements relatively to others using the anchor() function.

Different behaviors

Default OS stylesheet

In all browsers, the operational system has a unique stylesheet for all the elements in the UI. In order to remove the variable differences between them, the recommendation is to use some sort of CSS reset/normalizer like modern-normalize or normalize.css.

Regardless, this can get you off guard if the project you are working doesn’t apply or partially apply them.

Safari iOS default stylesheet for buttons of submit type

In the example screenshot above, you can see Apple defines an 1em default vertical padding for form submit button elements, and below Android Chrome defines a 6px for them.

Chrome android default stylesheet for buttons of submit type

PWA support

Apple deliberately choose to push back PWA apps for quite some time now. The most recent news is from a iOS 17.4 update that killed its poor PWA instalation but gladly got reverted quickly due the community backslash.

In terms of features, Safari is catching up with them, recently supporting push notifications and background sync. You can refer to WhatCanPWACanDoToday, which is a great website that shows the current state of PWA support in different browsers and platforms.