Skip to content

Instantly share code, notes, and snippets.

@frencojobs
Created September 11, 2024 16:57
Show Gist options
  • Save frencojobs/5a579a1cb199a84f908ddfe6e894cf40 to your computer and use it in GitHub Desktop.
Save frencojobs/5a579a1cb199a84f908ddfe6e894cf40 to your computer and use it in GitHub Desktop.
A script to manage appearance (dark mode) and accent-color.
// @ts-nocheck
// biome-ignore lint: Handwritten script for performance.
;(function () {
try {
const d = document.documentElement
const ls = localStorage
// Run `fn` without css transitions.
// See https://paco.me/writing/disable-theme-transitions.
const withoutTransitions = (fn) => {
const styleTag = document.createElement('style')
styleTag.appendChild(
document.createTextNode(`* {
-webkit-transition: none !important;
-moz-transition: none !important;
-o-transition: none !important;
-ms-transition: none !important;
transition: none !important;
}`),
)
document.head.appendChild(styleTag)
fn()
const _ = window.getComputedStyle(styleTag).opacity
document.head.removeChild(styleTag)
}
// Appearance - Light, Dark, System.
const appearanceKey = 'data-appearance'
const appearanceOptions = ['light', 'dark', 'system']
const resolvedAppearanceOptions = appearanceOptions.slice(0, -1)
let appearanceValue = undefined
// Get system's appearance value.
const query = '(prefers-color-scheme: dark)'
const matchMedia = window.matchMedia(query)
const getSystemAppearance = () =>
matchMedia.media !== query || matchMedia.matches ? appearanceOptions[1] : appearanceOptions[0]
const applyAppearance = () => {
let resolvedValue = appearanceValue
// Resolve `system` to actual value.
if (!appearanceValue || appearanceValue === 'system') {
resolvedValue = getSystemAppearance()
}
// If the value is not valid, fallback to the first option.
if (!resolvedAppearanceOptions.includes(resolvedValue)) {
resolvedValue = appearanceOptions[0]
}
withoutTransitions(() => {
d.style.colorScheme = resolvedValue
d.setAttribute(appearanceKey, resolvedValue)
// Support class based dark mode as well.
d.classList.add(resolvedValue)
d.classList.remove(resolvedAppearanceOptions.filter((value) => value !== resolvedValue))
})
}
// Accent color.
const accentColorKey = 'data-accent-color'
const defaultAccentColor = 'gray'
let accentColorValue = undefined
const applyAccentColor = () => {
let resolvedAccentColorValue = accentColorValue
// If the value is not valid, fallback to default color.
if (!accentColorValue) {
resolvedAccentColorValue = defaultAccentColor
}
withoutTransitions(() => {
d.setAttribute(appearanceKey, resolvedAccentColorValue)
})
}
// Read from localStorage on load.
appearanceValue = ls.getItem(appearanceKey)
accentColorValue = ls.getItem(appearanceKey)
// Apply the values on the first run.
applyAppearance()
applyAccentColor()
// Handle storage/other events.
const handleUpdateEvents = ({key, newValue}, updateStorage = false) => {
if (key === appearanceKey) {
appearanceValue = newValue
applyAppearance()
} else if (key === accentColorKey) {
accentColorValue = newValue
applyAccentColor()
} else return
if (updateStorage) {
ls.setItem(appearanceKey, newValue)
}
}
// Listen to localStorage changes (works only on different browser tabs).
window.addEventListener('storage', (event) => handleUpdateEvents(event))
// Listen to updates from other scripts (works only on same browser tab).
window.addEventListener('set-theme', (event) => handleUpdateEvents(event.detail, true))
// Listen to system appearance changes.
matchMedia.addEventListener('change', () => applyAppearance())
} catch (e) {}
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment