automatic (prefers-color-scheme) and manual CSS theme switcher with vanilla javascript snippet
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.theme input { | |
display: none; | |
} | |
.theme { | |
margin: 0 0.3em 0 0; | |
} | |
.theme input + label { | |
color: #f90; | |
cursor: pointer; | |
border-bottom: 3px solid transparent; | |
padding-bottom: 0.1em; | |
margin-left:0.6em; | |
} | |
.theme input:hover + label, | |
.theme input:checked + label { | |
border-bottom: 3px solid #eee; | |
color: #eee; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- <head> needs to contain the alternative style sheet --> | |
<style media="all"> | |
# normal CSS | |
</style> | |
<style id="css_alt" media="speech"> | |
# alt CSS | |
</style> | |
<!-- example of container and sibling that will auto-include the theme form --> | |
<div id="header-forms"> | |
<!-- theme form will be inserted here if the selectors in the JS code below are unmodified --> | |
<form id="search" role="search" method="get" action="/search/"> | |
<label for="qsub"> | |
<input type="submit" value="search" id="qsub" name="qsub" /> | |
<svg width="16" height="16"> | |
<use xlink:href="#icon-search"></use> | |
</svg> | |
</label> | |
<input type="search" placeholder="search..." value="" name="q" id="q" title="Search for:" /> | |
</form> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var DEFAULT_THEME = 'dark'; | |
var ALT_THEME = 'light'; | |
var STORAGE_KEY = 'theme'; | |
var theme_container = document.getElementById("header-forms"); | |
var theme_insbefore = document.getElementById("search"); | |
var colorscheme = []; | |
var mql = window.matchMedia('(prefers-color-scheme: ' + ALT_THEME + ')'); | |
function indicateTheme(mode) { | |
for(var i = colorscheme.length; i--; ) { | |
if(colorscheme[i].value == mode) { | |
colorscheme[i].checked = true; | |
} | |
} | |
} | |
function applyTheme(mode) { | |
var st = document.getElementById('css_alt'); | |
if (mode == ALT_THEME) { | |
st.setAttribute('media', 'all'); | |
} | |
else { | |
st.setAttribute('media', 'speech'); | |
} | |
} | |
function setTheme(e) { | |
var mode = e.target.value; | |
var mql = window.matchMedia('(prefers-color-scheme: ' + ALT_THEME + ')'); | |
/* user wants == mql match => remove storage */ | |
if ((mode == DEFAULT_THEME && !mql.matches) || (mode == ALT_THEME && mql.matches)) { | |
localStorage.removeItem(STORAGE_KEY); | |
} | |
else { | |
if(confirm("I\'ll need to store your choice in your browser, in a place called localStorage.\n\nAre you OK with this?")) { | |
localStorage.setItem(STORAGE_KEY, mode); | |
} | |
} | |
autoTheme(mql); | |
} | |
function autoTheme(e) { | |
var mode = DEFAULT_THEME; | |
try { | |
var current = localStorage.getItem(STORAGE_KEY); | |
} catch(e) { | |
var current = DEFAULT_THEME; | |
} | |
if ( current != null) { | |
mode = current; | |
} | |
else if (e.matches) { | |
mode = ALT_THEME; | |
} | |
applyTheme(mode); | |
indicateTheme(mode); | |
} | |
function doTheme() { | |
var themeform = document.createElement('form'); | |
themeform.className = "theme"; | |
themeform.innerHTML='<svg width="16" height="16"><use xlink:href="#icon-contrast"></use></svg>'; | |
theme_container.insertBefore(themeform, theme_insbefore); | |
var schemes = ["dark", "light"]; | |
for (var i = 0; i < schemes.length; i++) { | |
var span = document.createElement('span'); | |
themeform.appendChild(span); | |
var input = document.createElement('input'); | |
input.name = 'colorscheme'; | |
input.type = 'radio'; | |
input.id = schemes[i] + input.name; | |
input.value = schemes[i]; | |
span.appendChild(input); | |
var label = document.createElement('label'); | |
label.htmlFor = input.id; | |
label.innerHTML = schemes[i]; | |
span.appendChild(label); | |
} | |
colorscheme = document.getElementsByName('colorscheme'); | |
for(var i = colorscheme.length; i--; ) { | |
colorscheme[i].onclick = setTheme; | |
} | |
autoTheme(mql); | |
mql.addListener(autoTheme); | |
} | |
var test = 'ping'; | |
try { | |
localStorage.setItem(test, test); | |
localStorage.removeItem(test); | |
doTheme(); | |
} catch(e) { | |
console.log('localStorage is not available, manual theme switching is disabled'); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment