<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

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.