JavaScript Tutorial

JavaScript Project: Theme Switch

Toggle light and dark on the page and remember the choice in localStorage.

What you will build

A page with a Light / Dark button. The colors swap immediately. Reload the preview and the last choice is still there, because it is stored in localStorage.

The theme is a class on document.documentElement (the html element). CSS custom properties change with that class. The script does not paint each heading by hand.

Open an example with Try it in JavaScript. That loads /javascript/try — a live page and a console.

Methods you will use

MethodJob on this page
classListToggles the dark class on <html>
localStorageSaves "light" or "dark" across reloads
clickRuns the toggle when the button is pressed
getItem / setItemReads the saved theme, then writes the new one
textContentUpdates the button label to match the next action

Apply the saved class before the first paint if you can. In this complete document the script sits at the end of the body, so you still set the class as the first line of the script to avoid a long flash.

Build in slices

Two color palettes on :root and on :root.dark. Body, card, and button all read the variables. No extra classes on those nodes.

Example

<style>
  :root {
    --bg: #fefce8;
    --card: #fffbeb;
    --ink: #1c1917;
    --accent: #854d0e;
  }
  :root.dark {
    --bg: #1c1917;
    --card: #292524;
    --ink: #fef3c7;
    --accent: #fbbf24;
  }
  body { background: var(--bg); color: var(--ink); margin: 0; }
  .card { background: var(--card); padding: 1rem; }
</style>
<article class="card">
  <p>Light studio colors. Add class dark on html to swap the palette.</p>
</article>

Restore first. If the stored value is "dark", add the class. Anything else, including a missing key, stays light.

Example

const root = document.documentElement;
const saved = localStorage.getItem("theme");
if (saved === "dark") root.classList.add("dark");

Toggle writes both the class and the storage key. Use the return value of classList.toggle: it is true when the class is now present.

Example

<style>
  :root { --bg: #fefce8; --ink: #1c1917; }
  :root.dark { --bg: #1c1917; --ink: #fef3c7; }
  body { background: var(--bg); color: var(--ink); }
</style>
<button id="theme" type="button">Use dark theme</button>
<script>
  const root = document.documentElement;
  const button = document.getElementById("theme");
  const saved = localStorage.getItem("theme");
  if (saved === "dark") root.classList.add("dark");

  function syncLabel() {
    const dark = root.classList.contains("dark");
    button.textContent = dark ? "Use light theme" : "Use dark theme";
  }

  button.addEventListener("click", function () {
    const dark = root.classList.toggle("dark");
    localStorage.setItem("theme", dark ? "dark" : "light");
    syncLabel();
  });

  syncLabel();
</script>

Toggle once, reload the editor at /javascript/try. The dark palette should return. Try it in JavaScript stores the document; a full reload of that tab still readslocalStorage for the same origin.

Complete document

This file is the switch you would keep. Sample copy sits on the card so you can see headings, a paragraph, and a link in both palettes. The storage key is theme.

Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Theme Switch</title>
  <style>
    :root {
      --bg: #fefce8;
      --card: #fffbeb;
      --ink: #1c1917;
      --muted: #78716c;
      --line: #fde68a;
      --accent: #854d0e;
      --accent-ink: #fffbeb;
    }
    :root.dark {
      --bg: #1c1917;
      --card: #292524;
      --ink: #fef3c7;
      --muted: #a8a29e;
      --line: #44403c;
      --accent: #fbbf24;
      --accent-ink: #1c1917;
    }
    body {
      margin: 0;
      min-height: 100vh;
      display: grid;
      place-items: center;
      background: var(--bg);
      color: var(--ink);
      font-family: "Segoe UI", system-ui, sans-serif;
    }
    .card {
      width: min(24rem, 92vw);
      background: var(--card);
      border: 1px solid var(--line);
      padding: 1.4rem 1.5rem 1.5rem;
    }
    h1 { margin: 0 0 0.4rem; font-size: 1.4rem; }
    p { margin: 0 0 0.85rem; line-height: 1.55; color: var(--muted); }
    a { color: var(--accent); }
    button {
      padding: 0.5rem 0.9rem;
      border: 0;
      background: var(--accent);
      color: var(--accent-ink);
      font: inherit;
      font-weight: 700;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <article class="card">
    <h1>Theme Switch</h1>
    <p>
      Light is the yellow studio. Dark keeps the same layout with stone backgrounds and amber accents.
      Reload the page after you toggle. The choice is stored in localStorage.
    </p>
    <p>
      The live editor is <a href="/javascript/try">/javascript/try</a>, not /try.
    </p>
    <button id="theme" type="button">Use dark theme</button>
  </article>
  <script>
    const root = document.documentElement;
    const button = document.getElementById("theme");
    const KEY = "theme";

    function apply(theme) {
      if (theme === "dark") root.classList.add("dark");
      else root.classList.remove("dark");
      button.textContent = theme === "dark" ? "Use light theme" : "Use dark theme";
    }

    const saved = localStorage.getItem(KEY);
    apply(saved === "dark" ? "dark" : "light");

    button.addEventListener("click", function () {
      const next = root.classList.contains("dark") ? "light" : "dark";
      localStorage.setItem(KEY, next);
      apply(next);
    });
  </script>
</body>
</html>

Storage is a string

localStorage only stores strings. Save "dark" and "light", not a boolean. getItem returns null when the key is missing. Treat that as light.

The key is shared with every page on the same origin. If another demo also uses "theme", they will overwrite each other. For a larger site you would namespace the key, for examplestudygrid-js-theme. This project keeps the short name.

Common mistakes

  • Toggling a class on body while the CSS is written for :root.dark. Nothing changes.
  • Saving true without quotes. You get the string "true". Compare it on purpose or save "dark".
  • Calling classList.toggle("dark") and then always setItem("theme", "dark"). Light would never persist.
  • Wrapping the first paint in setTimeout. The light theme would flash every reload.
  • Using sessionStorage by accident. A new tab would forget the choice. This project wants localStorage.

Practice tasks

  1. Honor prefers-color-scheme: dark when nothing is stored yet. After the first click, storage wins.
  2. Add a third High contrast theme and cycle Light → Dark → High contrast → Light.
  3. Show the stored value in a status line under the button. Preview at /javascript/try.

FAQ: JavaScript Project: Theme Switch

Common questions about this page.

What is the StudyGrid JavaScript tutorial?

The StudyGrid JavaScript tutorial is a full beginner track: variables, functions, arrays, if/else, the DOM, events, storage, and fetch. Each chapter has copy-and-run examples.

Should I run javascript theme project examples locally for better learning?

Yes. Use the browser editor on StudyGrid for a quick check, then Download the example and run it on your computer. Local runs show real errors and the real toolchain, which is one of the fastest ways to learn javascript theme project in this JavaScript JavaScript lesson (JavaScript Project: Theme Switch).

Is the JavaScript editor the same as Try Python?

No. Try JavaScript is a live page plus a console at /javascript/try. Try Python stays at /try. JavaScript lessons never open the Python editor.

Do I need to install anything to learn JavaScript?

No. Open a chapter, click Try it in JavaScript, and the page and console update in the browser.

Where should I start the JavaScript tutorial?

Start at JavaScript Intro, then Where To, Output, and Syntax. After variables and functions, continue to the DOM and events. Use Next at the bottom of each chapter.

Is the JavaScript tutorial free?

Yes. The JavaScript studio on StudyGrid (studygrid.in) is free: dashboard, chapters, and the live editor.