Keep data when the page is closed with localStorage: save text when something changes, load it when the page opens, and save lists as JSON.
A game that remembers your best score, a to-do list that is still there tomorrow: the browser keeps a little data for each site.
Every time a page opens, its variables start from nothing. localStorage is a small store the browser keeps for each website, so data survives a reload and a visit next week:
localStorage.setItem("visits", visits); // save, under a name
let visits = Number(localStorage.getItem("visits")); // load (null if nothing was saved)
localStorage.removeItem("visits"); // forget it
It only keeps text. To save a list, turn it into JSON text first, and turn it back when you load it:
localStorage.setItem("books", JSON.stringify(books));
books = JSON.parse(localStorage.getItem("books"));
Here, each Run is a new visit to the page. The playground keeps your saved data for each step, as a browser keeps it for a website.
Make your prediction first. Help opens once you've locked it in.
<p>Visits: <span id="count">0</span></p>
<script>
let visits = Number(localStorage.getItem("visits"));
visits = visits + 1;
localStorage.setItem("visits", visits);
document.querySelector("#count").textContent = visits;
</script>
Number(...) turns the saved text into a number; when nothing is saved yet it gives 0.
Press Run, add two books, then press Run again. Then try Start again and Run once more: what comes back?
<h1>Books I have read</h1>
<input id="title" placeholder="A book">
<button id="add">Add</button>
<ul id="books"></ul>
<button id="clear">Start again</button>
<script>
const list = document.querySelector("#books");
let books = [];
const saved = localStorage.getItem("books");
if (saved !== null) {
books = JSON.parse(saved);
}
function show() {
list.textContent = "";
for (const title of books) {
const li = document.createElement("li");
li.textContent = title;
list.append(li);
}
}
document.querySelector("#add").addEventListener("click", function () {
const box = document.querySelector("#title");
books.push(box.value);
localStorage.setItem("books", JSON.stringify(books));
box.value = "";
show();
});
document.querySelector("#clear").addEventListener("click", function () {
books = [];
show();
});
show();
</script>
<h1>Books I have read</h1>
<input id="title" placeholder="A book">
<button id="add">Add</button>
<ul id="books"></ul>
<button id="clear">Start again</button>
<script>
const list = document.querySelector("#books");
let books = [];
const saved = localStorage.getItem("books");
if (saved !== null) {
books = JSON.parse(saved);
}
function show() {
list.textContent = "";
for (const title of books) {
const li = document.createElement("li");
li.textContent = title;
list.append(li);
}
}
document.querySelector("#add").addEventListener("click", function () {
const box = document.querySelector("#title");
books.push(box.value);
localStorage.setItem("books", JSON.stringify(books));
box.value = "";
show();
});
document.querySelector("#clear").addEventListener("click", function () {
books = [];
show();
});
show();
</script>
Load, turn back, save.
Two copies of the list.
Saved the wrong way.
Match each line with what it does.
The first Run shows 1 books. The next Run shows a red error: "Book 1" is not valid JSON. Click the line that is wrong.
Forget the saved copy.
Start again empties the list on the page, but the saved copy is still there.
Add localStorage.removeItem("books"); inside the Start again handler.

Save on every change.
A box for the reader's name: save its value whenever it changes, and put the saved value back when the page opens.
An "input" event that runs localStorage.setItem("reader", reader.value); and, at the start, if getItem("reader") is not null, set reader.value to it.

Make Start again forget the saved list too.
This ticks when your page has all of the above.
Add a box for the reader's name that is remembered between visits.
This ticks when your page has all of the above.
The clicker keeps a best score, but it is lost on every visit. Make it last: load it when the page opens and save it whenever it goes up.
Save the best score so it is still there after Run.
This ticks when your page has all of the above.
For Vue: save inside watch(tasks, …, { deep: true }) and load in setup. Next on the pathway: Your site, for real, where your page goes online.
Students predict a visit counter after three runs, save a reading list as JSON so it survives Run, find a list saved without JSON.stringify, make Start again forget the saved copy, remember a name, and keep a best score. About 50 minutes.
Open the teacher guide: answers, solutions, slides and a worksheet.
An optional lesson at the end of the DOM course, and the "saving" step for every route through the Web pathway. localStorage is a small store each browser keeps per website: setItem(name, text), getItem(name) (null when nothing is saved) and removeItem(name). It keeps text only, so lists and objects go through JSON.stringify and JSON.parse (met in the fetch lesson). The planted bug saves a list without stringify: the first run looks fine, the second fails, because the bad text is still saved. The playground's hint says to clear it.
The pattern for any app: load once when the page opens, save every time the data changes. In the playground, each Run is a new visit; the playground keeps each step's saved data for this browser (the preview page cannot use the real store, so a stand-in behaves the same way). On a real site it is the browser's own localStorage, per site and per browser: nothing is shared between computers or people, and clearing browsing data deletes it. Don't save anything private.
For Vue students (the to-do app): load the list in setup (const tasks = ref(JSON.parse(localStorage.getItem("tasks")) || [])), then watch(tasks, (t) => localStorage.setItem("tasks", JSON.stringify(t)), { deep: true }). Preact and Svelte have their own versions (useEffect, $effect).
Students forget that saving is separate from the page: they change the list and expect it saved. Ask "which line saves it?" after every change they make.
Stretch: Save the to-do app from Apps with Vue (see the note above). Save a whole object (name and score) as one JSON item.
Support: Give the load and save lines on the board; students choose where each goes.
Next on the Web pathway: Your site, for real (accessibility, then putting the page online). Sharing saved data between people needs a server: Web apps with Flask.