Define the Theme Color for Safari 26
tl;dr
On Safari 26 the theme color for the browser UI is either taken from the site’s body background color or the topmost position-fixed element with a background-color if there is one. theme-color meta tag and theme_color manifest member do not affect the browser theme color.
→ Here’s a demo ←
If you are using Safari 26 you might have noticed that on some websites, the browser’s chrome (the UI containing the URL bar, back buttons, tabs etc) has a solid background color matching the page colors. My colleague Marc and I tried to find out how that is defined and embarked on a journey that was longer than expected.
Say good-bye to theme-color 👋
In the past, the Theme-Color meta tag was used for that, at least in certain scenarios (like iOS fullscreen “PWAs”, or in other browsers like Vivaldi). But, this meta tag was dropped with Safari 26.
Personal sidenote: I think I’m in favor of dropping this meta tag as it was never implemented across the board. I would argue that the
theme_colormanifest member would be a much better place to define it, but then again, what about different values for dark/light mode? sigh
Back to Safari 26
So, if theme_color is not what is used on Safari to define the header background color, what is?
I went inspecting a few websites where Safari colorizes the header:
It’s easy to hate on many of MacOS/iOS 26 design decisions, but boy that looks slick as hell if you ask me.
I found a similarity: Safari simply uses the background color of the <body> element. You can even change the background color using Safari Dev Tools and see the changes reflected in the browser UI.
I thought I cracked the case, but alas – I was wrong. As it turns out there are other websites where Safari clearly does not simply use the body background-color. Check out ankerbrot.at for example:
The <body> background is beige, the site’s page header is dark red and so is the browser chrome! What is going on?
The answer (simplified)
After some more digging I think the rules Safari applies for its colorized chrome are:
- take the page’s
<body>background-color, except when - the page has a
position: fixedelement with a background-color at the very top* of the page
* in the sense of block-direction start
This fixed element must be 100% wide and have a minimum height of 6px for Safari to accept it as the base color for it’s chrome. Position: sticky does not count.
Applying a custom theme-color
Based on these two observations, I tried to trick the browser into appling a custom color even though the site I’m working on does not have a fixed header nor a desirable background color for this use-case. I just needed to find a way to hide the 6px-high fixed element for users while Safari still accepts it for it’s chrome color.
After more experimentation, here’s a list of declarations that cause Safari to not accept the fixed element to colorize the browser.
opacity: 0visibility: hiddendisplay: nonez-index: -1scale: 0transform: scaleX(-100%)transform: translateX(-100%)clip: rect(1px, 1px, 1px, 1px)
I ran out of options to hide an element for users while still have it visually ready for the browser’s magic.
Scroll-driven animations to the rescue
I did not care if the element is visible on page load/when scrolled to the very top, we can safely hide it below the “real” header using z-index. But as soon as the user scrolls down, we need to hide the element. Turns out, this is easy to achieve with scroll-driven animations.
.custom-theme-color {
position: fixed;
top: 0px;
background-color: #bada55;
height: 6px;
width: 100%;
animation-name: hide-on-scroll;
animation-duration: 0.1s;
animation-timeline: scroll();
z-index: 1; /* make sure this is lower than the actual header z-index */
}
@keyframes hide-on-scroll {
0% {
transform: translateY(0);
}
1% {
transform: translateY(0);
}
1.1% {
transform: translateY(-100px);
}
100% {
transform: translateY(-100px);
}
}
With this solution, the .custom-theme-color element is moved out of the viewport as soon as user starts scrolling while Safari still accepts the background color for the primary color of the browser chrome.
Another interesting observation: if you add a border-bottom to the fixed element, Safari will take that color instead of the background of the element. 🤷
→ Here’s the link to the demo again, if you want to play around with it (Safari 26 required to see the effect).
Disclaimer and Caveats
The solution breaks if the page is reloaded at a different scroll position than “top”. Maybe an interactive (JS-based) solution would be better suited, but I wanted to solve it with CSS only.
We do not use this in production (yet) and needs more testing, especially on iOS.