Skip to content

Instantly share code, notes, and snippets.

@petermolnar
Last active December 11, 2023 13:06
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save petermolnar/d7ccaffadb92bf6c3d3615ed92832669 to your computer and use it in GitHub Desktop.
Save petermolnar/d7ccaffadb92bf6c3d3615ed92832669 to your computer and use it in GitHub Desktop.
automatic (prefers-color-scheme) and manual CSS theme switcher with vanilla javascript snippet
.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;
}
<!-- <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>
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