Nahorniak Templates
База референсів шаблонів і компонентів
Назад до шаблону · Runok - Web Agency HTML5 Template
c223

theme-toggle-switch

Інтерактив·Шаблон: Runok - Web Agency HTML5 Template·Складність анімації: subtle·Адаптивний: Так
theme-toggle-switch

Файли-джерела

  • index.htmlheader.header.sticky-active

Бібліотеки

jquery

Summary

A floating pill toggle (#theme-toogle) anchored bottom-right, that flips the document data-theme attribute between dark and light. Choice is persisted in localStorage; first paint respects prefers-color-scheme. Light-mode styling is a single _light-mode.scss override sheet keyed off [data-theme="light"], so no class swapping is required at runtime.

HTML structure (minimal)

<div id="theme-toogle" class="switcher-button">
  <div class="switcher-button-inner-left"></div>
  <div class="switcher-button-inner"></div>
</div>

Key SCSS tokens

#theme-toogle {
  position: fixed; bottom: 30px; left: 30px;
  width: 60px; height: 30px; border-radius: 999px;
  background: var(--rr-color-bg-1);
  border: 1px solid var(--rr-color-border-1);
  cursor: pointer; z-index: 990;
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 4px;
  .switcher-button-inner,
  .switcher-button-inner-left {
    width: 22px; height: 22px; border-radius: 50%;
    background: var(--rr-color-theme-primary);
    transition: transform .3s ease;
  }
}
[data-theme="light"] #theme-toogle .switcher-button-inner {
  transform: translateX(-30px);
}

Animation logic

const storageKey = 'theme-preference';

const getColorPreference = () => {
  if (localStorage.getItem(storageKey)) return localStorage.getItem(storageKey);
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
};

const reflectPreference = () => {
  document.firstElementChild.setAttribute('data-theme', theme.value);
  document.querySelector('#theme-toogle')?.setAttribute('aria-label', theme.value);
};

const setPreference = () => {
  localStorage.setItem(storageKey, theme.value);
  reflectPreference();
};

const onClick = () => {
  theme.value = theme.value === 'light' ? 'dark' : 'light';
  setPreference();
};

const theme = { value: getColorPreference() };
reflectPreference(); // run before window load → no FOUC

$(window).on("load", function () {
  reflectPreference();
  document.querySelector('#theme-toogle').addEventListener('click', onClick);
});

Notable details

  • reflectPreference() is called BEFORE $(window).on("load", …) so the data-theme attribute is on <html> before any CSS paints — eliminates the first-paint flash typical of theme toggles.
  • The pattern uses data-theme attribute selectors instead of class swapping — every light-mode rule lives in _light-mode.scss as [data-theme="light"] selector { … }. Cleaner than .theme-light cascade overrides.
  • All visual assets that have light/dark variants (icons, logos) ship as paired <img class="dark-img"> + <img class="light-img">; the light-mode CSS hides one and reveals the other — no JS image swap.

Use when

  • Templates that ship both palettes and want a no-flicker theme switcher without React.
  • Content sites where users may have a prefers-color-scheme preference but the in-app toggle should override it.

Caveats

  • The element id #theme-toogle is misspelled in the source ("toogle" instead of "toggle"). Fix carefully — it's referenced from both HTML and JS.
  • localStorage is read on every page load even before the DOM is ready, so SSR'd implementations may see a brief mismatch; fine for static HTML templates.
  • No system-change listener — if the user toggles their OS theme mid-session the page won't follow until the next reload.