<theme-switcher modes="light, dark" dataAttr="data-theme" current="dark" aria-label="Change to light mode" aria-live="polite">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="square" stroke-linejoin="round">
<circle cx="12" cy="12" r="5" />
<path d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4" />
</svg>
</theme-switcher>
:root {
--clr-bg: 220 39% 15%;
--clr-bg-alt: 220 39% 30%;
--clr-dark: 220 39% 10%;
--clr-main: 0 100% 67%;
--clr-light: 44 100% 88%;
--clr-bright: 43 100% 65%;
--clr-link: 199 100% 65%;
--clr-link-hover: var(--clr-light);
--clr-text-base: 220 39% 95%;
}
[data-theme="light"] {
--clr-bg: 38 22% 95%;
--clr-bg-alt: 220 39% 90%;
--clr-main: 0 100% 67%;
--clr-alt-1: 199 100% 20%;
--clr-alt-2: 43 100% 65%;
--clr-link: 199 100% 40%;
--clr-link-hover: var(--clr-dark);
--clr-text-base: 199 100% 15%;
--clr-text-reverse: 220 39% 95%;
}
* {
box-sizing: border-box;
}
html {
height: 100%;
}
body {
min-height: 90vh;
display: grid;
place-items: center;
background: hsl(var(--clr-bg));
color: hsl(var(--clr-text-base));
font: 400 clamp(1.3rem, 2vw, 1.5rem) / 1.5 sans-serif;
}
theme-switcher {
width: 2rem;
display: grid;
place-items: center;
position: relative;
padding: 0;
background: none;
border: 0;
border-radius: 0.5rem;
transition: all 0.2s ease-in-out;
cursor: pointer;
color: hsl(var(--clr-link));
}
theme-switcher svg {
width: 100%;
height: auto;
object-fit: contain;
stroke: hsl(var(--clr-link));
transition: all 0.2s ease-in-out;
}
theme-switcher:hover svg,
theme-switcher:focus svg {
stroke: hsl(var(--clr-link-hover));
}
View Compiled
class ThemeSwitcher extends HTMLButtonElement {
constructor() {
super();
// Bindings
this.swap = this.swap.bind(this);
this.ariaLabel = this.ariaLabel.bind(this);
// Get static attributes
this.dataAttr = this.getAttribute("dataAttr");
this.mode1 = this.getAttribute("modes")
.split(",")
.map((index) => index.trim())[0];
this.mode2 = this.getAttribute("modes")
.split(",")
.map((index) => index.trim())[1];
// Grab dom elements
this.body = document.querySelector("body");
// Check for a default theme setting on the body
if (this.body.getAttribute(this.dataAttr)) {
this.current = this.body.getAttribute(this.dataAttr);
}
// sets the click listener to fire the swap function
this.addEventListener("click", this.swap);
}
// Swaps the attribute
ariaLabel(state) {
this.setAttribute("aria-label", `${state} mode`);
}
// Fires all the attribute swapping
swap() {
if (this.current === this.mode1) {
this.current = this.mode2;
this.ariaLabel(this.mode1);
this.body.setAttribute(this.dataAttr, `${this.mode2}`);
} else {
this.current = this.mode1;
this.ariaLabel(this.mode2);
this.body.setAttribute(this.dataAttr, `${this.mode1}`);
}
}
// Observe current
static get observedAttributes() {
return ["current"];
}
// Get current
get current() {
return this.getAttribute("current");
}
// Set current
set current(val) {
return this.setAttribute("current", val);
}
}
customElements.define("theme-switcher", ThemeSwitcher, { extends: "button" });
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.