Back
Cito Theme Controller
What
- Utilities for building theme switching for web app.
- Framework/library agnostic.
Idea
- Use root element html's custom attribute
data-theme
to keep state of the theme.
- Utilise CSS custom properties to match to the currently selected state:
:root:not([data-theme='light']), [data-theme='dark'] {...}
- In case toggling is not needed and Javascript can be dropped, just use appropriate CSS
selectors, e.g.
prefers-color-scheme
and ignore this file.
How to use
- When DOMContentLoaded invoke
detectColorScheme
- Checks system settings and localStorage, then applies the theme
- Defaults to
light
theme
- When user wants to change theme invoke
toggleTheme
getCurrentTheme
gets currently stored theme, useful to display for UI.
- signature:
getCurrentTheme(): "Light" | "Dark" | "no theme set"
Javascript
/**
* Cito Theme Controller
* Version 1.0.0
* Author: ilkka.kuivanen@me.com
*/
const LIGHT = 'light';
const DARK = 'dark';
const THEME = 'theme';
function getLocalStorage() {
return localStorage.getItem(THEME);
}
function setLocalStorage(v) {
localStorage.setItem(THEME, v);
}
function setTheme(v) {
const html = document.querySelector('html');
if (html !== null) {
html.dataset['theme'] = v;
}
setLocalStorage(v);
}
function getCurrentThemeValue() {
return (document.querySelector('html')?.dataset['theme'] ??
'no theme set');
}
export function getCurrentTheme() {
const theme = getCurrentThemeValue();
return theme.charAt(0).toUpperCase() + theme.slice(1);
}
export function toggleTheme() {
const currentTheme = getCurrentThemeValue();
if (currentTheme === LIGHT) {
setTheme(DARK);
}
else {
setTheme(LIGHT);
}
}
export function detectColorScheme() {
let theme = LIGHT;
// Get current media value, default to "light"
let currentlySetSystemColorScheme = LIGHT;
if (!window.matchMedia) {
return;
}
else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
currentlySetSystemColorScheme = DARK;
}
const currentLocalStorageTheme = getLocalStorage();
// Prefer localStorage, otherwise go with system scheme
if (currentLocalStorageTheme === LIGHT ||
currentLocalStorageTheme === DARK) {
theme = currentLocalStorageTheme;
}
else {
theme = currentlySetSystemColorScheme;
}
document.querySelector('html')?.setAttribute('data-theme', theme);
setLocalStorage(theme);
}
Typescript
/**
* Cito Theme Controller
* Version 1.0.0
* Author: ilkka.kuivanen@me.com
*/
const LIGHT = 'light';
const DARK = 'dark';
const THEME = 'theme';
function getLocalStorage() {
return localStorage.getItem(THEME);
}
function setLocalStorage(v: string) {
localStorage.setItem(THEME, v);
}
function setTheme(v: string) {
const html = document.querySelector('html');
if (html !== null) {
html.dataset['theme'] = v;
}
setLocalStorage(v);
}
function getCurrentThemeValue() {
return (
document.querySelector('html')?.dataset['theme'] ??
'no theme set'
);
}
export function getCurrentTheme() {
const theme = getCurrentThemeValue();
return theme.charAt(0).toUpperCase() + theme.slice(1);
}
export function toggleTheme() {
const currentTheme = getCurrentThemeValue();
if (currentTheme === LIGHT) {
setTheme(DARK);
} else {
setTheme(LIGHT);
}
}
export function detectColorScheme() {
let theme = LIGHT;
// Get current media value, default to "light"
let currentlySetSystemColorScheme: typeof LIGHT | typeof DARK = LIGHT;
if (!window.matchMedia) {
return;
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
currentlySetSystemColorScheme = DARK;
}
const currentLocalStorageTheme = getLocalStorage();
// Prefer localStorage, otherwise go with system scheme
if (
currentLocalStorageTheme === LIGHT ||
currentLocalStorageTheme === DARK
) {
theme = currentLocalStorageTheme;
} else {
theme = currentlySetSystemColorScheme;
}
document.querySelector('html')?.setAttribute('data-theme', theme);
setLocalStorage(theme);
}