@brandonaaron/dark-pref v1.0.1
Dark Pref
Provides a preference manager (DarkPref), an optional web component (DarkPrefToggleElement), an optional base web component (DarkPrefToggleBaseElement), and a tiny render blocking script to avoid a potential initial flash of content.
- No dependencies, ES Modules, TypeScript, Unit Tests
- Persists to
localStorageif possible (uses__dark-pref__key) - Keeps multiple tabs in sync via
localStorage - Provides information on system preference and user preference
- Prioritizes the system dark mode preference
- Emits a
darkpref:synccustom event on the document with current state as thedetail darkclass name toggled on<html>element
See how it works on my personal site (it is in the top right corner). Details below about how I implemented it in Astro.
Quick Start
Add this to the <head>:
<!-- tiny render blocking script to avoid flash of content -->
<script src="https://cdn.jsdelivr.net/npm/@brandonaaron/dark-pref@1.0.0/dist/DarkPref.blocking.js"></script>
<!-- rest of the exports could be bundled with your favorite bundler too -->
<script src="https://cdn.jsdelivr.net/npm/@brandonaaron/dark-pref@1.0.0/dist/DarkPrefToggleElement.js" type="module"></script>
<style>
:root { /* optional css variables */
--dark-pref-when-light-color: #999; /* defaults to #000 */
--dark-pref-when-dark-color: #ccc; /* defaults to #fff */
}
</style>Then add the included custom element to the <body>:
<dark-pref-toggle>
<!-- Optional slots "light" and "dark" (they default to a sun and moon svg) -->
<span slot="light">Switch to dark mode</span>
<span slot="dark">Switch to light mode</span>
</dark-pref-toggle>Check the examples folder for other uses like creating your own custom element or not using custom elements at all.
Docs: DarkPref
DarkPref.current
A getter that includes three props: system (boolean), user (DarkPrefUserSetting), and isDark (boolean)`.
DarkPref.reset()
Clear out any stored preference and resync. This always triggers the darkpref:sync event on the document.
DarkPref.sync()
Used to trigger a resync of the current state. This always triggers the darkpref:sync event on the document.
DarkPref.toggle(pref)
Changes the user preference. The pref argument is optional but should be a boolean or null if passed (DarkPrefUserSetting type).
The DarkPref.current.user value will be null when their dark preference aligns with the system preference.
Docs: DarkPrefToggleBaseElement
Provides a custom element to extend for your own custom element. Automatically hookes up a click and darkpref:sync event handlers on connectedCallback. Does not register a tag.
ariaPressedForCurrentState (getter)
Provides a value for aria-pressed attribute. The button is considered pressed only if the user has a different prefence from the current system preference.
ariaLabelForCurrentState (getter)
Provides a value for aria-label attribute.
connectedCallback()
Connects click and darkpref:sync event handlers.
disconnectedCallback()
Disconnects click and darkpref:sync event handlers.
sync()
This needs to be implemented by the extending class. This should check DarkPref.current and implement necessary changes to the DOM.
toggle(pref)
Changes the user preference. The pref argument is optional but should be a boolean or null if passed (DarkPrefUserSetting type).
Docs: DarkPrefToggleElement
Provides a quick and easy button to toggle dark mode on/off. Provides two slots and two css variables for customization. This extends the DarkPrefToggleBaseElement. Automatically registers the tag name dark-pref-toggle.
There is a dist/custom-elements.json in the npm package.
CSS Variables
--dark-pref-when-dark-color: Controls thecolorwhen dark is preferred (defaults to#fff)--dark-pref-when-light-color: Controls thecolorwhen light is preferred (defaults to#000)
Slots
light: Defaults to a sun svg icondark: Defults to a moon svg icon
Usage with Astro
I'm using this on my personal site which is built with Astro and TailwindCSS.
First I npm installed it:
npm install @brandonaaron/dark-prefI copied over dist/DarkPref.blocking.js to the public directory and included that via an is:inline script tag in the <head> of my main layout:
<script is:inline src="/DarkPref.blocking.js"></script>Then I created a DarkPrefToggle.astro component with the following:
---
---
<script>
import { DarkPref } from '@brandonaaron/dark-pref/dist/DarkPref.js'
import '@brandonaaron/dark-pref/dist/DarkPrefToggleElement.js'
// The following lines is applicable only if you're using transitions
document.addEventListener('astro:after-swap', () => { DarkPref.sync() })
</script>
<style>
:root {
--dark-pref-when-light-color: theme("colors.zinc.800");
--dark-pref-when-dark-color: theme("colors.zinc.200");
}
</style>
<dark-pref-toggle class="w-[24px] h-[24px]"></dark-pref-toggle>My tailwind.config.js also has the following to ensure it works with html element dark mode class toggling:
module.exports = {
//...
darkMode: 'class',
//...
}Docker
Run docker compose up to get a working dev environment. This will start a dev server on port 8888.
Run docker compose run --build dev npm run test to just run the unit tests in isolation.
There is a config for a vscode devcontainer too. The devcontainer uses the same Dockerfile/docker-compose config but does not start a dev server. Instead open a terminal within vscode and run npm start (or npm run test, etc).