{"id":343850,"date":"2021-07-13T07:58:33","date_gmt":"2021-07-13T14:58:33","guid":{"rendered":"https:\/\/css-tricks.com\/?p=343850"},"modified":"2021-08-06T04:40:44","modified_gmt":"2021-08-06T11:40:44","slug":"meta-theme-color-and-trickery","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/meta-theme-color-and-trickery\/","title":{"rendered":"Meta Theme Color and Trickery"},"content":{"rendered":"\n

Starting with Version 15, Safari supports the theme-color<\/code> <meta><\/code> tag both on macOS and iOS. That\u2019s exciting news because now the first desktop browser supports this <meta><\/code> tag and it also supports the media<\/code> attribute and the prefers-color-scheme<\/code> media feature.<\/p>\n\n\n\n

I never really took much note of the theme-color<\/code> meta tag, but now is a good time to learn about its features and limitations and try to discover some interesting use cases.<\/p>\n\n\n

Heads up! Safari removed support for the theme-color<\/code> meta tag in Safari Technology Preview (127)<\/a>. That was only temporary, starting with release 128 it supports it again.<\/em><\/p>\n\n\n\n\n\n

Features and limitations<\/h3>\n\n\n

Here\u2019s how I\u2019ve been using the theme-color<\/code> meta tag for the past few years: just a good \u2018ol hex code for the content<\/code> attribute.<\/p>\n\n\n\n

<meta name=\"theme-color\" content=\"#319197\"><\/code><\/pre>\n\n\n\n
\"\"<\/figure>\n\n\n\n

According to tests I made<\/a> earlier this year, this works in Chrome, Brave and Samsung Internet on Android, installed PWAs in Chrome and now also in Safari Technology Preview.<\/p>\n\n\n\n

\"\"
Hex color support is great in all supported browsers.<\/figcaption><\/figure>\n\n\n

CSS color support<\/h3>\n\n\n

One of the first questions that came to my mind was \u201cCan we use color keywords, hsl()<\/code>, rgb()<\/code>, too?\u201d According to the HTML spec<\/a>, the value of the attribute can be any CSS color. I\u2019ve created this theme-color<\/code> testing CodePen<\/a> to verify that.<\/p>\n\n\n\n

<meta name=\"theme-color\" content=\"hsl(24.3, 97.4%, 54.3%)\"><\/code><\/pre>\n\n\n\n
\"Blank
The theme-color<\/code> meta tags supports CSS colors in any form: keywords, rgb()<\/code>, hsl()<\/code> or hex code.<\/figcaption><\/figure>\n\n\n\n
\"Blank
Looking at Chrome 90 on an Android Galaxy S20<\/figcaption><\/figure>\n\n\n\n

All supported browsers also support hsl()<\/code> and rgb()<\/code>. This is awesome because it allows us to do some pretty cool stuff with JavaScript. We\u2019ll talk about that later, but first let\u2019s look at some limitations.<\/p>\n\n\n

Transparency<\/h4>\n\n\n

HEX codes, rbg()<\/code>, hsl()<\/code> and keywords are well and consistently supported, but colors that include transparency: not so much. Actually, they are supported in most browsers, but the results aren\u2019t very consistent and sometimes unexpected.<\/p>\n\n\n\n

transparent<\/code> is a CSS color and used in the theme-color<\/code> meta tag most browsers do what you\u2019d expect. All regular mobile browsers don\u2019t change color and display the default tab bar, but Safari on macOS and the Chrome Canary PWA on macOS turn the tab bar black. The PWA on Android falls back to theme-color<\/code> defined in the manifest.json<\/code>, which we\u2019ll talk about in a bit.<\/p>\n\n\n\n

\"Examples
Browser with a transparent theme-color<\/code> meta tag<\/figcaption><\/figure>\n\n\n\n

All browsers interpret hsla()<\/code> and rgba()<\/code>, but they set the alpha value to 1. The only exception is Safari on macOS; it interprets the transparency, but it seems like the transparent color has a black baseline. This has the effect that the light orange color looks like dark orange.<\/p>\n\n\n\n

\"Same
hsla()<\/code> applied to the theme-color<\/code> meta tag<\/figcaption><\/figure>\n\n\n

New color functions<\/h4>\n\n\n

Safari 15 is the first browser to support lab()<\/code>, lch()<\/code>, and hwb()<\/code> color functions. These functions work if you use them in CSS, but not if you use them in the theme-color<\/code> meta tag.<\/p>\n\n\n\n

All three declarations work fine in Safari 15:<\/p>\n\n\n\n

body {\n  background-color: hwb(27 10% 28%);\n  background-color: lch(67.5345% 42.5 258.2);\n  background-color: lab(62.2345% -34.9638 47.7721);\n}<\/code><\/pre>\n\n\n\n

If you use any of the new color functions in the theme-color<\/code> meta tag, Safari doesn\u2019t interpret them and falls back to its own algorithm of picking the color. It\u2019s likely that Safari uses the background color of your <body><\/code> for the theme-color<\/code>, which means that you might get the expected result without defining the theme-color<\/code> explicitly.<\/p>\n\n\n\n

<meta name=\"theme-color\" content=\"lab(29.2345% 39.3825 20.0664)\"><\/code><\/pre>\n\n\n\n
\"Green<\/figure>\n\n\n\n

Please be aware that at the time of writing Safari 15 is the only browser to support<\/a> these new colors functions.<\/p>\n\n\n

currentColor<\/code><\/h4>\n\n\n

If CSS colors are supported, currentColor<\/code> should work, too, right? No, unfortunately not in any browser. It\u2019s probably an uncommon use case, but I would expect that we can set the theme-color<\/code> to the current color of the <body><\/code> or <html><\/code> element.<\/p>\n\n\n\n

<style>\n  body {\n    color: blue;\n  }\n<\/style>\n\n<meta name=\"theme-color\" content=\"currentColor\"><\/code><\/pre>\n\n\n\n

I found a ticket in the WebKit bug tracker titled \u201c<meta name=\"theme-color\" content=\"...\"><\/code> should also support CSS currentcolor<\/code>.”<\/a> Support might change in the future, if someone picks the ticket up.<\/p>\n\n\n

Prohibited colors<\/h3>\n\n\n

When I was testing CSS color keywords, I used the color red<\/code> and it didn\u2019t work. First, I thought that keywords weren\u2019t supported, but blue<\/code>, hotpink<\/code>, and green<\/code> worked fine. As is turns out, there\u2019s a narrow range of colors that Safari doesn\u2019t support<\/a>, colors that would get in the way of using the interface. red<\/code> doesn\u2019t work because it\u2019s visually too close to the background color of the close button in the tab bar. This limitation is specific to Safari, in all other supported browsers any color seem to work fine.<\/p>\n\n\n\n

\"Wbite
If you set the theme-color<\/code> to red<\/code>, Safari uses any color it deems appropriate.<\/figcaption><\/figure>\n\n\n

Custom properties<\/h3>\n\n\n

I don\u2019t know enough about the internals of browsers and custom properties and if it\u2019s even possible to access custom properties in the <head><\/code>, but I tried it anyway. Unfortunately, it didn\u2019t work in any browser.<\/p>\n\n\n\n

<style>\n  :root {\n    --theme: blue;\n  }\n<\/style>\n\n<meta name=\"theme-color\" content=\"var(--theme)\"><\/code><\/pre>\n\n\n\n

That\u2019s pretty much everything I wanted to know about basic support of the theme-color<\/code> meta tag. Next, let\u2019s see how to and how not to implement dark mode for the tab bar.<\/p>\n\n\n

Dark mode<\/h3>\n\n\n

Safari 15 is the first desktop browser to support the media<\/code> attribute and the prefers-color-scheme<\/code> media feature on theme-color<\/code> meta tags. Starting with version 93, Chrome supports it too, but only for installed progressive web apps.<\/p>\n\n\n\n

According to the web app manifest page on web.dev<\/a>, if you define multiple theme-color<\/code> meta tags, browsers pick the first tag that matches.<\/p>\n\n\n\n

<meta name=\"theme-color\" content=\"#872e4e\" media=\"(prefers-color-scheme: dark)\"><\/code><\/pre>\n\n\n\n

I was eager to find out what happens in browsers that don\u2019t support the media<\/code> attribute. I\u2019ve created a demo page for testing dark mode<\/a> that includes the meta tags above and also allows you to install the site as a PWA. The webmanifest.json<\/code> includes another color definition for the theme-color.<\/p>\n\n\n\n

{\n  \"name\": \"My PWA\",\n  \"icons\": [\n    {\n      \"src\": \"https:\/\/via.placeholder.com\/144\/00ff00\",\n      \"sizes\": \"144x144\",\n      \"type\": \"image\/png\"\n    }\n  ],\n  \"start_url\": \"\/theme-color-darkmode.html\",\n  \"display\": \"standalone\",\n  \"background_color\": \"hsl(24.3, 97.4%, 54.3%)\",\n  \"theme_color\": \"hsl(24.3, 97.4%, 54.3%)\"\n}<\/code><\/pre>\n\n\n\n

Here\u2019s how supported browsers display the tab bar in light mode. It doesn\u2019t matter if a browser supports the media attribute or not, it will interpret the first meta tag regardless.<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

Here\u2019s how the tab bar on the same page looks like in dark mode. These results are more interesting because they vary a bit. The Canary PWA and Safari support and show the dark color. All mobile browsers use their default dark tab bar styling, except for Samsung Internet, which uses the light styling because it doesn\u2019t support the prefers-color-scheme<\/code> media feature. (TIL<\/abbr>: This should change in the near future<\/a>.)<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

I did one last test. I wanted to see what happens if I only define a theme color for dark mode, but access the page in light mode.<\/p>\n\n\n\n

<meta name=\"theme-color\" content=\"#872e4e\" media=\"(prefers-color-scheme: dark)\"><\/code><\/pre>\n\n\n\n
\"\"<\/figure>\n\n\n\n

These results surprised me the most because I expected all mobile browsers to ignore the media attribute and just use the dark color in the meta tag regardless, but ordinary Chrome Canary completely ignores the whole meta tag, even though it doesn\u2019t support the media<\/code> attribute. As expected, both Canary PWAs fall back to the color defined in the manifest file.<\/p>\n\n\n\n

The other interesting thing is that Safari displays a theme-color<\/code> even though I haven\u2019t defined one for light mode. That\u2019s because Safari will pick a color on its own, if you don\u2019t provide a theme-color<\/code>. In this case, it uses the background color of the page, but it also might use the background color of the <header><\/code> element, for example.<\/p>\n\n\n\n

If you want to define a theme color for light and dark mode, your best bet is to define both colors and use the first meta tag as a fallback for browsers that don\u2019t support the media feature.<\/p>\n\n\n\n

<meta name=\"theme-color\" content=\"#319197\" media=\"(prefers-color-scheme: light)\">\n<meta name=\"theme-color\" content=\"#872e4e\" media=\"(prefers-color-scheme: dark)\"><\/code><\/pre>\n\n\n\n

Safari has proven that theme-color<\/code> works great on desktop browsers, too. I\u2019m sure that designers and developers will find many creative ways to use this meta tag, especially considering that the value can be changed via JavaScript. I\u2019ve collected and created some interesting demos for your inspiration.<\/p>\n\n\n

Demos and use cases<\/h3>\n\n\n
\n \n Theming <\/summary>\n \n\n

poolsuite.net<\/a> provides different themes for the site and changes the theme-color accordingly.<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

Max B\u00f6ck<\/a> also changes the theme-color<\/code> on his website<\/a> when you change the theme.<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

<\/p>\n\n\n<\/details>\n\n\n

\n \n Page theming <\/summary>\n \n\n

Most websites don\u2019t provide custom themes, but you can still give your pages that certain something. Dave uses different key colors<\/a> in his blog posts for links and icons, and now also in the tab bar.<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n<\/details>\n\n\n
\n \n Gradients <\/summary>\n \n\n

If you\u2019re using gradients on your page, you can highlight your styling by making the gradient span the whole browser. The theme-color<\/code> meta tag doesn\u2019t support gradients, but you can use the same color for the meta tag and the start color of the gradient of you page\u2019s background.<\/p>\n\n\n\n

<meta name=\"theme-color\" content=\"rgb(0, 235, 255)\">\n\n<style>\n  body {\n    background: linear-gradient(rgb(0, 235, 255), #08124a);\n  }\n<\/style><\/code><\/pre>\n\n\n\n
\"\"<\/figure>\n\n\n<\/details>\n\n\n
\n \n Form validation <\/summary>\n \n\n

I built this proof of concept of a form that changes theme-color<\/code><\/a> on form validation. It starts with a blue tab bar which turns red if the submitted data is invalid or green if it\u2019s valid.<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n
const email = document.querySelector('input')\nconst themeColor = document.querySelector('meta[name=\"theme-color\"]')\nconst msg = document.querySelector('[aria-live]')\nlet color = '#FA0000'\nlet message = 'Error message'\n\ndocument.querySelector('button').addEventListener('click', (e) => {\n  e.preventDefault()\n\n  email.reportValidity()\n  email.setAttribute('aria-invalid', true)\n\n  if (email.validity.valid) {\n    color = '#00FF00'\n    message = \"Success message!\"\n    email.setAttribute('aria-invalid', false)\n  }\n\n  msg.textContent = message\n  themeColor.setAttribute('content', color)\n});<\/code><\/pre>\n\n\n<\/details>\n\n\n
\n \n Disco mode <\/summary>\n \n\n

I\u2019m not saying that you should, but you could put your site in 💃 Disco Mode 🕺 by combining setInterval<\/code> and hsl()<\/code><\/a> colors.<\/p>\n\n\n\n

\/*\nInspired by https:\/\/twitter.com\/argyleink\/status\/1408184587885309952\n*\/\n\nconst motion = window.matchMedia(\"(prefers-reduced-motion: no-preference)\");\n\n\/\/ Check if users don't have a preference for reduced motion\nif (motion.matches) {\n  let scheme = document.querySelector('meta[name=\"theme-color\"]')\n  let hue = 0\n  let color\n\n  setInterval(() => {\n    color = `hsl(${hue+=5} 50% 30%)`\n    document.body.style.background = color;\n    scheme.setAttribute('content', color)\n  }, 50)<\/code><\/pre>\n\n\n\n
\n \n Scrolling <\/summary>\n \n\n

Stuart had a great idea, he suggested changing theme color on scroll<\/a>. I built this quick prototype<\/a>, again using hsl()<\/code> colors.<\/p>\n\n\n\n

Max built a demo<\/a> in which he changes the theme-color<\/code> according to the background color of the current section in the viewport using Intersection Observer.<\/p>\n\n\n\n

const setThemeColor = (color) => {\n  const meta = document.querySelector('meta[name=\"theme-color\"]')\n  if (meta) {\n    meta.setAttribute('content', color)\n  }\n}\n\nif (\"IntersectionObserver\" in window) {\n  const observer = new IntersectionObserver(entries => {\n      entries.forEach(entry => {\n        const { isIntersecting, target } = entry\n        if (isIntersecting) {\n          const color = window.getComputedStyle(target).getPropertyValue(\"background-color\");\n          setThemeColor(color)\n        }\n      })\n  }, {\n    root: document.getElementById('viewport'),\n    rootMargin: \"1px 0px -100% 0px\",\n    treshold: 0.1\n  })\n  \n  document.querySelectorAll('.section').forEach(section => {\n    observer.observe(section)\n  })\n}<\/code><\/pre>\n\n\n<\/details>\n\n\n
\n \n Extracting color <\/summary>\n \n\n

Another interesting idea is to extract the dominant or average color<\/a> from your header images automatically and use it as the theme-color<\/code>.<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n
<script type=\"module\">\n  import fastAverageColor from \"https:\/\/cdn.skypack.dev\/fast-average-color@6.4.0\";\n  const fac = new fastAverageColor();\n    \n  fac.getColorAsync(document.querySelector('img'))\n    .then(color => {\n      document.querySelector('meta[name=\"theme-color\"]').setAttribute('content', color.rgba)\n    })\n    .catch(e => {\n      console.log(e);\n    });\n<\/script> \n  \n<img src=\"\/amy-humphries-2M_sDJ_agvs-unsplash.jpg\" alt=\"A sea star on blue sand.\" \/><\/code><\/pre>\n\n\n\n

<\/p>\n\n\n<\/details>\n\n\n

That is just a handful of ideas, but I already like where this is going and I\u2019m sure that you\u2019ll come up with even more creatives ways of using the theme-color<\/code> meta tag.<\/p>\n\n\n

Resources<\/h3>\n\n\n