惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
WordPress大学
WordPress大学
小众软件
小众软件
Cloudbric
Cloudbric
AWS News Blog
AWS News Blog
腾讯CDC
量子位
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Vulnerabilities – Threatpost
Scott Helme
Scott Helme
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
Jina AI
Jina AI
Attack and Defense Labs
Attack and Defense Labs
S
SegmentFault 最新的问题
Simon Willison's Weblog
Simon Willison's Weblog
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
Google Online Security Blog
Google Online Security Blog
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
罗磊的独立博客
L
LINUX DO - 最新话题
博客园 - Franky
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
The Last Watchdog
The Last Watchdog
J
Java Code Geeks
AI
AI
C
Cisco Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cyber Attacks, Cyber Crime and Cyber Security
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
雷峰网
雷峰网
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
I
Intezer
S
Securelist

endtimes.dev

Happy new year | endtimes.dev why lowercase letters save data Practical betterments | endtimes.dev Short posts and some site updates Input Output | endtimes.dev Actually, dark mode can save the planet Why your website should be under 14kB in size Why your website should work without Javascript. An HTML and CSS only dark-mode toggle button. you can use css to remove the double-tap zoom feature on iOs Missing Dice | endtimes.dev Can Dark Mode Save Battery Life and Human Civilzation? Your DNS has a significant impact on site speed. Add spaces to the dock on MacOS You can leave out <html>, <head>, and <body> tags. Listen for changes between dark and light mode with javascript Use a keyboard shortcut to quickly toggle light and dark mode on MacOs. Why Your Website Should Use Dithered Images You can restore the startup chimes on MacOS Emoji Clipboard | endtimes.dev You can use Emojis as Favicons .dev and HSTS preload | endtimes.dev Prod — Block Distractions. Achieve your goals. Pattern generator | endtimes.dev Green Quarantine | endtimes.dev Qwitter Bot | endtimes.dev
HTML and CSS only multiple color scheme picker
2022-08-03 · via endtimes.dev

It's 2022 and lots of websites support dark mode. Around 7% of websites did in 2021

Many of these websites even have a toggle for changing between light and dark mode.

Dark mode is great, many people prefer it, it's easier on the eyes, and it extends battery life.

But what about other color schemes?

For my website missingdice.com I wanted to support more than two color schemes.

It's a website with tools for playing games — rolling dice, spinning wheels, that sort of thing — it's simple and it's for fun.

The site fulfils quite simple needs, so the same tool might be used for a kid's party, or by a group playing dungeons and dragons, or possibly some low stakes betting.

So i decided to include light, dark, party, vegas, and dungeons and dragons color schemes, and a high contrast mode — useful for some people with visual impairments, sunglasses, or if the screen is in direct sunlight.

I previously shared a tutorial on how to make a html and css only dark mode toggle — some found it useful and requested I publish a tutorial for the color-scheme picker. So here it is.

It covers much of the same ground as the dark mode toggle. It works fine without JavaScript, but also has some progressive enhancements for users with JavaScript enabled.

Here's what we're going to make… #

Here's a CodePen with the final product (plus some styling)

A color scheme picker that…

  • works well without javascript
  • defaults to a user's dark mdoe preference
  • supports windows high-contrast mode and forced-colors

…as well as some progressive enhancements for users with JavaScript enabled…

  • Saves user preferences
  • Updates the <meta rel="theme"/> and <body> tag with the correct color scheme.

…and some graceful degradations for users on older browsers…

  • falls back to light mode if css variables (custom properties) aren't supported
  • remove the toggle if css variables aren't supported.

What kind of input to use #

Since we have multiple values, and can only select one at a time — we have only two choices. Radio Buttons or a Select Box.

Due to our no-js limitation — we need to use an input who's state can be accessed in CSS.

Select box <option> elements are nested inside a <select> element — this makes their state inaccessible to the rest of the page using CSS.

So we're left with radio buttons — which is a good choice anyway!

How it works #

To change our page's color scheme using just css — we need to make sure our radio buttons appear before everything else in our document.

We have a radio input for each of our color schemes. Then we can target the rest of our document with the :checked css selector.

if the radio input with id=blue is selected, we use the :checked css selector to target the rest of our document. The css selector looks like this: #blue:checked ~ *.

We can't style the <body> tag based on the radio inputs. So we create a <div> that our site's content goes inside, then style that <div> to fill the screen.

This causes a small issue when overscrolling and on non-rectangular screens, but we'll solve that later with some progressive enhancements.

We can then visually hide the <input> elements, and use their corresponding labels as buttons to toggle color scheme.

The labels can be placed anywhere on the page and will work fine so long as the label's for attribute matches it's corresponding radio input's id.

<body>

<input id="dark" class="color-scheme-button" name="color-scheme" type="radio"/>
<input id="light" class="color-scheme-button" name="color-scheme" type="radio"/>
<input id="blue" class="color-scheme-button" name="color-scheme" type="radio"/>

<!-- more radio buttons go here -->

<div class="color-scheme-wrapper">

<label for="dark">dark mode</label>
<label for="light">light mode</label>
<label for="blue">blue mode</label>

<!-- Site Content -->

</div>

<style>

.color-scheme-wrapper {
min-height:100vh;
background:white;
color:black;
}

.color-scheme-wrapper {
background:white;
color:black;
}

#dark:checked ~ * {
background:black;
color:white;
}

#blue:checked ~ * {
background:blue;
color:white;
}

</style>

</body>

This code works. But we still need to add a few more things. We'll start with CSS variables.

CSS variables (custom properties) #

css variables save us a lot of clutter — without them we'd have to restyle every component on our website for each color scheme.

Instead we can define some variables and then change their values based on the selected color scheme.

For the sake of this tutorial we'll keep things simple — one color for the background and one for text.

First we create values for the default color scheme — this will be the light theme:


:root {

--background:white;
--text:black;

}

Then we do the same for the other color schemes — this time applying the variables to every element that comes after the checked radio button that matches that scheme.

If a user checks a radio button, that color schemes' variables are applied to the whole site.


#dark:checked ~ * {

--bg:black;
--text:white;

}

#blue:checked ~ * {

--bg:blue;
--text:white;

}

.color-scheme-wrapper {

background:var(--bg);
color:var(--text);

}

This works — but we need a fallback for browsers that don't support CSS variables — which as of writing is true for ~3.5% of web users.

css is very forgiving — if a browser comes accross a property it doesn't understand it will ignore it and keep going.

So, we can make our site look good on older browsers by repeating the property twice, once with a fallback color, and then again with our variable, like so:

.color-scheme-wrapper {

background:white;
background:var(--bg);

color:black;
color:var(--text);

}

We can also hide our color scheme buttons for users who's browsers don't support them:

.color-scheme-button,
.color-scheme-button + label
{

display:none;

}

@supports(--css:custom-properties) {
.color-scheme-button,
.color-scheme-button + label
{
display:block;
}
}

Defaulting to user's preferred color scheme #

Our current setup doesn't support preferred color scheme — meaning if a user who's has dark mode enabled on their device visits our site, they'll be met with our light theme.

Using the prefers-color-scheme css media query — we can make sure our visitor's preferred color scheme is the default.

We do this by "swapping" the variables of the light theme and dark theme if the user's device has dark-mode enabled — making the default color scheme "dark" and the dark theme "light".


:root {

--bg:white;
--text:black;

}

@media (prefers-color-scheme: dark) {
:root {

--bg:black;
--text:white;

}
}

#dark:checked ~ * {

--bg:black;
--text:white;

}

@media (prefers-color-scheme: dark) {

#dark:checked ~ * {

--bg:white;
--text:black;

}

}

That's great, now if a user has dark mode enabled, the site will default to dark mode — however, if the user then wants to manually select light mode they need to select the radio button with the "Dark mode" label.

A small snag, but it has a simple fix.

We can use the same media queries to change the text in the <label> elements.

We create two <span> elements with the text "Light mode" and "Dark mode" in the labels — then create classes to display the relevant text based on the user's preferences.


<body>

<div class="color-scheme-wrapper">

<label for="light">
<span class="dark-mode-hide">Light Mode</span>
<span class="light-mode-hide">Dark Mode</span>
</label>

<label for="dark">
<span class="dark-mode-hide">Dark Mode</span>
<span class="light-mode-hide">Light Mode</span>
</label>

</div>

</body>

<style>

.light-mode-hide {
display:none
}

@media (prefers-color-scheme: dark) {

.light-mode-hide {
display:inline
}

.dark-mode-hide {
display:none
}
}

</style>

Now our label's change names to match.

Forced Colors and High Contrast Mode #

For a variety of reasons, some users prefer specific color schemes to be applied to every web page they visit.

For these users selecting a color scheme on our website will have no effect.

Using css mediwe can remove the toggle for users with who have enabled either: forced colors or windows high contrast mode.

We can also use these css media queries to make other subtle changes to our design.

Windows High Contrast mode #

-ms-high-contrast non-standard (microsoft only) css media feature. Introduced on Internet Explorer 11 on Windows 8 and continues to be supported on Edge.

@media (-ms-high-contrast: active) {

.color-scheme-button,
.color-scheme-button + label
{

display:none;

}

}

Forced colors #

Forced colors allows users to select their own color scheme for the web. It's often used to create high contrast color schemes, but can be used to make color schemes for other purposes too.


@media (forced-colors: active) {

.color-scheme-button,
.color-scheme-button + label
{

display:none;

}
}

Progressive Enhancements #

Theme color meta tag #

The theme-color meta tag attribute adds color to the browser's user interface (ui) — usually to the browser toolbar. See examples of theme-color in action here

It looks like this:

<meta name="theme-color" content="white" />

You can also use media queries with these meta tags and have them match the user's preferred color scheme:

<meta name="theme-color" media="(prefers-color-scheme: light)" content="white">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="black">

Styling the body tag #

In some browsers the color of the <body> tag will be visible when the user overscrolls or if the screen has large rounded corners — like the new iphones and pixel phones. So we should also style the body tag with our default color schemes:

body {

background:white;

}

@media (prefers-color-scheme: dark) {

background:black;

}

These are great for setting defaults — but when our users change the color scheme the theme-color meta tags and the body color will stay the same.

To fix this requires JavaScript — so we need to write some code to update the meta tags.

Enhancements with JavaScript #

First we'll create an object with keys matching the ids of the radio buttons — the values are the colors we want to use to set the theme colors.


var themeColors = {

dark: "black",
light: "white",
blue: "blue"

}

We need to access the meta tags and the body in our javascript. To keep our code simple I've given the meta tags ids:

<meta id="light-theme-meta-tag" name="theme-color" media="(prefers-color-scheme: light)" content="white">
<meta id="dark-theme-meta-tag" name="theme-color" media="(prefers-color-scheme: dark)" content="black">

Then we access them like so:

var lightThemeMetaTag = document.getElementById('light-theme-meta-tag')
var darkThemeMetaTag = document.getElementById('dark-theme-meta-tag')
var body = document.body

Then we'll write a function that updates our meta tags and our body color. It takes user selected theme as a parameter.

We'll also add a variable selectedTheme to store the currently selected theme so it's accessible to other functions — we'll see why shortly


var selectedTheme = 'light'

function updateTheme(theme) {

var color = themeColors[theme]

lightThemeMetaTag.setAttribute("content", color);
darkThemeMetaTag.setAttribute("content", color);
body.style.backgroundColor = color;

selectedTheme = theme

}

Then we’ll listen for changes to our radio inputs.

If a change is detected, we’ll call the updateTheme function with id of the selected radio input.


var radios = document.querySelectorAll('input[type=radio][name="color-scheme"]');

radios.forEach(radio => radio.addEventListener('change', (event) => {

var theme = event.target.id
updateTheme(theme)

}));

That updates our meta tags to match the currently selected theme.

There's one small problem though — on dark mode the values for our light and dark theme are the wrong way round!

So we’ll add some code that swaps the colors round if dark mode is enabled. It will also listens in the device's preferred color scheme and update accordingly. For instance, some people have their devices set to shift to dark mode at certain times.

First we'll write a function that swaps the light and dark colors around in our themeColors object.


function swapLightAndDark(){

var light = themeColors.light
var dark = themeColors.dark
themeColors.dark = light
themeColors.light = dark

}

Then we add code the swaps the themes round if the device is in dark mode — and swaps them again if the user's device changes it's prefered color scheme.

Changes to the prefers-color-scheme variable also needs to call our setTheme function to trigger the changes. This is where the selectedTheme variable comes in handy.


var preferedColorScheme = window.matchMedia('(prefers-color-scheme: dark)');
var dark = preferedColorScheme.matches

if(dark){
swapLightAndDark()
}

preferedColorScheme.addEventListener('change', () => {

swapLightAndDark()
setTheme(selectedTheme)

});

Remembering user preferences #

The biggest issue with not using JavaScript is that our user's preferences won't be remembered between visits — or even between pages on our site.

So, for users with JavaScript enabled we'll use local storage to store their preference.

First we'll write a function that saves the color scheme to local storage. It takes the current theme as a parameter:

function saveThemeToLocalStorage(theme){

if(localStorage){
localStorage.setItem('color-scheme', theme);
}

}

Then another function that checks if the user has a saved color scheme in local storage — if they do we'll update the theme and make sure we set that theme's radio input to checked.


function getThemeFromLocalStorage(){
if(localStorage){

var savedColorScheme = localStorage.getItem('color-scheme');

if(savedColorScheme){
var radioButton = document.getElementById(savedColorScheme);
radioButton.checked = true;
updateTheme(savedColorScheme)

}

}
}

Putting it all together #

Then we can those function in our previously written code. Putting it all together looks like this:

var themeColors = {

dark: "black",
light: "white",
blue: "blue"

}

var lightThemeMetaTag = document.getElementById('light-theme-meta-tag')
var darkThemeMetaTag = document.getElementById('dark-theme-meta-tag')
var body = document.body

var selectedTheme = 'light'

function updateTheme(theme) {

var color = themeColors[theme]

lightThemeMetaTag.setAttribute("content", color);
darkThemeMetaTag.setAttribute("content", color);
body.style.backgroundColor = color;

selectedTheme = theme

saveThemeToLocalStorage(theme)

}

function swapLightAndDark(){

var light = themeColors.light
var dark = themeColors.dark
themeColors.dark = light
themeColors.light = dark

}

function saveThemeToLocalStorage(theme){

if(localStorage){
localStorage.setItem('color-scheme', theme);
}

}

function getThemeFromLocalStorage(){

if(localStorage){

var savedColorScheme = localStorage.getItem('color-scheme');

if(savedColorScheme){
var radioButton = document.getElementById(savedColorScheme);
radioButton.checked = true;
updateTheme(savedColorScheme)
}
}
}

var preferedColorScheme = window.matchMedia('(prefers-color-scheme: dark)');
var dark = preferedColorScheme.matches

if(dark){
swapLightAndDark()
}

preferedColorScheme.addEventListener('change', () => {
swapLightAndDark()
setTheme(selectedTheme)
});

var radios = document.querySelectorAll('input[type=radio][name="color-scheme"]');

radios.forEach(radio => radio.addEventListener('change', (event) => {

var theme = event.target.id
updateTheme(theme)

}));

// This must be called after the check for dark mode
getThemeFromLocalStorage()

Finally, in order to make sure our theme is loaded from local storage before the page renders, we need to place this JavaScript in a tag after the radio inputs, but before the rest of our page.

Ideally this is inlined, to stop the page rendering being delayed by an http requrest.


<body>

<input id="dark" class="color-scheme-button" name="color-scheme" type="radio"/>
<input id="light" class="color-scheme-button" name="color-scheme" type="radio"/>
<input id="blue" class="color-scheme-button" name="color-scheme" type="radio"/>

<script>
<!-- our color scheme script goes here -->
</script>

<div class="color-scheme-wrapper">

<!-- Site Content -->

</div>

That's it. #

That's it!. With the above code and some styling you can make a very usable color scheme picker that works for as many people as possible.

I've created a demo in codepen. It uses all the code above, but with some extra styling, and a dropdown menu for selecting the color schemes, here it is:

Some final thoughts #

Using a drop down for the color schemes #

I explored a number of ways of displaying the color scheme options. But in the end I chose to use a drop down with a standard looking radio button for each option.

This allowed me to display the full labels for each color scheme without taking over the whole screen — as well as a clear label explaining what the buttons do.

I experimented with

  • showing a preview of the color scheme next to each option
  • styling buttons without text with the color of each theme
  • creating a multi-toggle switch looking thing
  • having a single button that cycles through each color theme.

I showed these to friends, and they mostly didn’t realize what they we’re for changing the color theme. Showing previes of the color scheme was also hard to decipher, especially if the theme was similar to the current theme.

So, a drop down with clear text labels was the winner.

Saving color schemes without JS!? #

This color scheme picker was first created for missingdice.com. It's a project I work on when I don't feel like doing anything high stakes. It has lots of self-imposed rules about how it should work.

One of these rules is that every tool should work without JavaScript.

For instance, if a user has JavaScript disabled and they use the dice rolling tool. Instead of rolling dice on the client side, it submits a form to a server, which rolls the dice, and sends back a page with the result.

This works great! BUT, it means our lovely no-js color switcher effectively becomes useless. A user picks a color, then they submit the form, and their color preference is gone, the results of their roll are shown in the default mode.

So, how to get round this?

We place our entire page inside a <form>, and submit our users chosen color scheme along with the options for their dice roll! Then we make sure the server responds with results shown in their preferred color scheme.

I'll be adding that to the site soon, so stay tuned.

published
2 Aug 2022
modified
2 Aug 2022
author
Nathaniel
tags
posts post tutorial