Package Exports
- dark-theme-utils
- dark-theme-utils/theme-toggler
Readme
Dark Theme utils
Caution: Alpha state, needs tests..
A repository containing some useful utilities for dark mode.
All of these packages work everywhere, built on browser standards (Web Components).
With 1 small exception (ThemeMixin
), these are completely dependency-free and should work anywhere out of the box.
Features
- Theme Toggler webcomponent: UI component to toggle the theme
- Theme Mixin: Mixin to wrap your Web Components with that syncs an internal theme prop with global theme, for styling purposes.
- Anti-FART script (Flash of inAccurate coloR Theme)
Theme Toggler
Toggler UI component that toggles the theme between "light" to "dark".
Toggler Features
- Visual UI toggle
- Accessible
- Extendible
- Works everywhere
- Responds to space, enter and all 4 arrow keys
- Uses OS/Browser color-scheme preference by default
- Responds to OS/Browser color-scheme preference changes
- Stores user preference in localStorage when toggling
- Sync theme state by listening to global
theme
attribute, meaning multiple togglers on the same page will work too
Using the toggler is pretty straight forward. Import the custom element definition file.
<script type="module" src="dark-theme-utils/theme-toggler"></script>
<theme-toggler></theme-toggler>
Extending the toggler is also possible:
import { ThemeToggler } from 'dark-theme-utils';
class CustomThemeToggler extends ThemeToggler {
}
customElements.define('custom-theme-toggler', CustomThemeToggler);
Theme Mixin
When you have components utilizing ShadowDOM style encapsulation, you won't be able to style those components based on a global theme attribute out of the box.
ThemeMixin
is a mixin utility that, when wrapping your ShadowDOM components, will reflect the global theme attribute to a local theme attribute.
ThemeMixin depends on
@open-wc/dedupe-mixin
which is important for deduplicating multiple applications of this same mixin on a class. It relies on this dependency with a bare import specifier so for this component it is important that this is resolved to a relative/absolute path.
import { ThemeMixin } from 'dark-theme-utils';
class FooElement extends ThemeMixin(HTMLElement) {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
width: 20px;
height: 20px;
display: block;
background-color: black;
}
:host([theme="dark"]) {
background-color: white;
}
</style>
`;
}
}
As you can see, we have a selector :host([theme="dark"])
which utilizes the fact that the ThemeMixin syncs the global theme attribute to the component.
html[theme="dark"] :host
would not work.
When the global theme attribute is changed, all components applying
ThemeMixin
change their theme attribute automatically. Every implementing component ofThemeMixin
responds to aMutationObserver
that watches the global theme attribute.
Anti-FART script
FART (Flash of inAccurate coloR Theme) can happen when your page is rendering before the theme attribute has been set on the html
element.
This means it will render <html>
instead of <html theme="dark">
which causes light-mode to render.
Right after, the code setting the theme executes, changing it to <html theme="dark">
.
The user will see a flash of light theme because of this when (re)loading the page.
To prevent this from happening, we should set the initial theme attribute on the html
element before anything renders,
which can be done by putting this script in the <head>
of your page.
If you don't have any transpilation/compilation steps, you can put this in your .html
file(s).
If your HTML files are generated by a build-tool like Rollup or Webpack, or by a dev-server during local development, you can use the transform hooks of these tools to insert it into the <head>
:
<head>
<script type="module">
// make sure to resolve the bare module specifier if this is your final html output
import { preventFart } from 'dark-theme-utils';
preventFart();
</script>
</head>
Local dev
In order to contribute or run locally, take a look at our Contributing Guidelines