Dark mode toggle that remembers your choice
Adds a button at the top of the notes editor that switches the app between light and dark mode. Your choice is saved in the browser and applied on the next visit without a flash. Until you choose, the app follows your computer's light/dark setting.
What it does
- Add a button with id "theme-btn" in the editor's top bar, placed just before the Delete button, with the tooltip "Switch between light and dark mode".
- Button label: "🌙 Dark mode" when the current look is light, "☀️ Light mode" when it is dark. The aria-label is "Switch to dark mode" or "Switch to light mode" to match.
- Clicking the button switches the whole app between light and dark. It sets a data-theme attribute ("dark" or "light") on the root <html> element and saves the choice in localStorage under the key "simple-notes-app.theme".
- Before any choice is made, the app follows the OS setting (prefers-color-scheme). Once a choice is saved, it overrides the OS setting.
- CSS: define a dark palette under :root[data-theme="dark"]. The existing prefers-color-scheme: dark block applies only when :root does not have data-theme="light". Set color-scheme to light or dark to match. Dark palette: bg #1c1c1e, panel #242426, border #3a3a3c, text #eee, muted #999, accent #6b93ff, selected #2f3a55, danger #ff6b6b.
- A small inline script in <head>, run before the page is drawn, reads the saved theme and sets data-theme so there is no flash of the wrong theme. It tolerates localStorage errors.
- At startup, render the button label. Also re-render it when the OS light/dark setting changes.
- Cross-tab sync: on a "storage" event for the theme key, apply the new value, or remove data-theme if the value is missing or invalid, then re-render the button.
- Add a README note: click "🌙 Dark mode" at the top of the editor (or "☀️ Light mode" to switch back). The choice is remembered, and until one is made the app follows the computer's setting.
Safety check ✓ Safe
The change only touches styling, one button, and a localStorage entry for the theme, plus a README note. It makes no network requests, adds no dependencies, and has no unrelated changes. The AI said it could not test in a browser, so try it once.
Conversation that created this patch
@lena
Add a dark mode with a toggle button that is remembered.
AI
I added a dark mode with an on/off button, and the app remembers your choice.
- **The button:** It's at the top of the editor, next to Delete. It says **🌙 Dark mode** when the app is light and **☀️ Light mode** when it's dark. One click switches the whole app.
- **It's remembered:** Your choice is saved in your browser along with your notes, so the app opens the same way next time. It also opens straight in your chosen look, without flashing the other one first. If you have the app open in more than one tab, they all switch together.
- **Before you choose:** The app follows your computer's own light/dark setting, as it did before. Once you click the button, your choice wins over that setting.
I also added a short note about the button to the instructions file. I checked that the code has no errors, but this sandbox has no web browser, so I couldn't click through it myself. It's worth trying it once when you open the file.
Code changes (for the curious, 2 files) · raw
README.md +2 −0
@@ -12,6 +12,8 @@ A simple notes app that runs entirely in your web browser. There is nothing to i | |
2. Double-click `index.html`. It opens in your web browser (Chrome, Firefox, Edge, Safari, and others all work). | |
3. Click **+ New** and start writing. | |
| |
| + | Prefer a darker screen? Click **🌙 Dark mode** at the top of the editor (click **☀️ Light mode** to switch back). Your choice is remembered next time you open the app. Until you pick one, the app follows your computer's own light/dark setting. |
| + | |
Shortcut: **Ctrl + Alt + N** (or **Cmd + Option + N** on a Mac) creates a new note. | |
| |
## Where are my notes stored? |
index.html +68 −1
@@ -4,6 +4,15 @@ | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title>Notes</title> | |
| + | <script> |
| + | // Apply the remembered light/dark choice before the page is drawn, so it doesn't flash. |
| + | try { |
| + | var savedTheme = localStorage.getItem("simple-notes-app.theme"); |
| + | if (savedTheme === "dark" || savedTheme === "light") { |
| + | document.documentElement.setAttribute("data-theme", savedTheme); |
| + | } |
| + | } catch (e) {} |
| + | </script> |
<style> | |
:root { | |
--bg: #f6f6f4; | |
@@ -14,9 +23,24 @@ | |
--accent: #3b6ef5; | |
--selected: #e8eefe; | |
--danger: #d64545; | |
| + | color-scheme: light; |
| + | } |
| + | /* Dark colors: used when chosen with the toggle button, or when the |
| + | computer is set to dark mode and no choice has been made yet. */ |
| + | :root[data-theme="dark"] { |
| + | color-scheme: dark; |
| + | --bg: #1c1c1e; |
| + | --panel: #242426; |
| + | --border: #3a3a3c; |
| + | --text: #eee; |
| + | --muted: #999; |
| + | --accent: #6b93ff; |
| + | --selected: #2f3a55; |
| + | --danger: #ff6b6b; |
} | |
@media (prefers-color-scheme: dark) { | |
| − | :root { |
| + | :root:not([data-theme="light"]) { |
| + | color-scheme: dark; |
--bg: #1c1c1e; | |
--panel: #242426; | |
--border: #3a3a3c; | |
@@ -174,6 +198,7 @@ | |
<main id="editor"> | |
<div id="editor-top"> | |
<span id="status"></span> | |
| + | <button id="theme-btn" type="button" title="Switch between light and dark mode"></button> |
<button id="delete-btn" class="danger">Delete</button> | |
</div> | |
<input id="title" type="text" placeholder="Title" aria-label="Note title"> | |
@@ -190,6 +215,7 @@ | |
| |
var STORAGE_KEY = "simple-notes-app.notes"; | |
var SELECTED_KEY = "simple-notes-app.selected"; | |
| + | var THEME_KEY = "simple-notes-app.theme"; |
| |
var notes = []; | |
var selectedId = null; | |
@@ -204,6 +230,7 @@ | |
var statusEl = $("status"); | |
var deleteBtn = $("delete-btn"); | |
var placeholderEl = $("placeholder"); | |
| + | var themeBtn = $("theme-btn"); |
| |
// ---------- Storage ---------- | |
| |
@@ -387,8 +414,38 @@ | |
scheduleSave(); | |
} | |
| |
| + | // ---------- Light / dark mode ---------- |
| + | |
| + | var systemDark = window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null; |
| + | |
| + | function currentTheme() { |
| + | var chosen = document.documentElement.getAttribute("data-theme"); |
| + | if (chosen === "dark" || chosen === "light") return chosen; |
| + | return systemDark && systemDark.matches ? "dark" : "light"; |
| + | } |
| + | |
| + | function renderThemeButton() { |
| + | var dark = currentTheme() === "dark"; |
| + | themeBtn.textContent = dark ? "☀️ Light mode" : "🌙 Dark mode"; |
| + | themeBtn.setAttribute("aria-label", dark ? "Switch to light mode" : "Switch to dark mode"); |
| + | } |
| + | |
| + | function toggleTheme() { |
| + | var next = currentTheme() === "dark" ? "light" : "dark"; |
| + | document.documentElement.setAttribute("data-theme", next); |
| + | try { localStorage.setItem(THEME_KEY, next); } catch (e) {} |
| + | renderThemeButton(); |
| + | } |
| + | |
// ---------- Events ---------- | |
| |
| + | themeBtn.addEventListener("click", toggleTheme); |
| + | if (systemDark) { |
| + | var onSystemChange = function () { renderThemeButton(); }; |
| + | if (systemDark.addEventListener) systemDark.addEventListener("change", onSystemChange); |
| + | else if (systemDark.addListener) systemDark.addListener(onSystemChange); |
| + | } |
| + | |
$("new-btn").addEventListener("click", newNote); | |
$("placeholder-new").addEventListener("click", newNote); | |
deleteBtn.addEventListener("click", deleteNote); | |
@@ -420,6 +477,15 @@ | |
| |
// Keep multiple open tabs in sync. | |
window.addEventListener("storage", function (e) { | |
| + | if (e.key === THEME_KEY) { |
| + | if (e.newValue === "dark" || e.newValue === "light") { |
| + | document.documentElement.setAttribute("data-theme", e.newValue); |
| + | } else { |
| + | document.documentElement.removeAttribute("data-theme"); |
| + | } |
| + | renderThemeButton(); |
| + | return; |
| + | } |
if (e.key !== STORAGE_KEY) return; | |
var keep = selectedId; | |
load(); | |
@@ -429,6 +495,7 @@ | |
| |
// ---------- Start ---------- | |
| |
| + | renderThemeButton(); |
load(); | |
if (!findNote(selectedId)) { | |
var first = sortedNotes()[0]; |