HTML Introduction to Web Storage
Web Storage is a browser feature, introduced as part of the HTML5 specification, that lets a web page save small pieces of data directly in the visitor’s browser and read it back later — without sending it to a server or relying on cookies. It comes in two flavors, localStorage and sessionStorage, and it is what makes things like “remember my theme,” “keep my shopping cart,” or “restore my unsaved draft” possible even after a page is reloaded or reopened. This lesson introduces the concept, how it fits into the DOM and the browser, and — since actually reading and writing values is done with JavaScript — how to structure your HTML so a script can hook into it cleanly. The scripting details, such as localStorage.setItem(), belong to the JavaScript course; here you will learn what Web Storage is, how it behaves, and how to prepare your markup for it.
Overview: What Web Storage Is
Before HTML5, the only way a web page could remember anything about a visitor between page loads was cookies. Cookies are tiny (about 4KB), are sent to the server with every single HTTP request, and were never really designed to hold application data — just small identifiers. HTML5 introduced the Web Storage API to give pages a much larger, purely client-side place to store data: typically 5–10MB per origin depending on the browser, never automatically transmitted over the network, and organized as simple key-value pairs where both the key and the value are strings.
There are two storage areas, and the difference between them is entirely about lifetime and scope, not capacity or syntax:
localStorage— persists indefinitely, until a script or the user explicitly clears it. Data survives closing the browser and restarting the computer, and it is shared across every tab and window open to the same origin (same protocol, host, and port).sessionStorage— persists only for the lifetime of one browser tab. Closing that tab discards the data. Even a second tab open to the exact same page gets its own separatesessionStorage; it is not shared between tabs the waylocalStorageis.
Both are exposed to JavaScript as properties of the global window object (window.localStorage and window.sessionStorage), and both are scoped per origin — a page on https://example.com cannot read storage written by https://other-site.com, and even http://example.com and https://example.com count as different origins because the protocol differs.
Where Web Storage Fits in the Page Lifecycle
Web Storage has nothing to do with how the browser parses your HTML or builds the DOM tree — it does not add nodes, and there is no tag for it. Instead, it is a browser-provided storage bucket that scripts read from and write to, usually to decide what to render into the DOM. A typical flow looks like this: the browser parses the HTML and constructs the DOM; a script (inline or external) runs and checks storage for saved data; if data exists, the script updates elements already present in the DOM, for example by setting a class or filling in text, to reflect the saved state. This is why, as an HTML author, your job is to give the elements that Web Storage will affect stable, predictable id or data-* attributes — the JavaScript layer depends on your markup being consistent.
Syntax: The Web Storage “Shape”
Because localStorage and sessionStorage are JavaScript objects rather than HTML elements, there is no tag to memorize. What you do need to know as an HTML author is the shape of the API so you can plan your markup around it. Both objects share the exact same members:
| Member | What it does |
|---|---|
setItem(key, value) |
Saves a value, always converted to a string, under a key. |
getItem(key) |
Returns the stored value for a key, or null if it does not exist. |
removeItem(key) |
Deletes a single key/value pair. |
clear() |
Deletes everything in that storage area for the current origin. |
key(index) |
Returns the name of the key at a given position, used to loop over all stored keys. |
length |
The number of key/value pairs currently stored. |
Both keys and values are always strings. To store something structured, like an object or array, a script converts it to a JSON string before saving it and parses it back on read. As the HTML author, the practical takeaway is: settle on a naming scheme for your storage keys — for example, prefixing every key your page uses with myapp- to avoid colliding with other scripts on the same page — and give the DOM elements that will display or trigger that data clear, unique identifiers.
Examples
Example 1: A Theme-Preference Page Skeleton
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Reading Preferences</title>
</head>
<body>
<header>
<h1>Reading Preferences</h1>
<button id="theme-toggle-btn" type="button">Toggle Dark Mode</button>
</header>
<main>
<p id="theme-status">Current theme: light</p>
</main>
</body>
</html>
Result: The browser renders a heading “Reading Preferences,” a button labeled “Toggle Dark Mode,” and a paragraph reading “Current theme: light.” Nothing here reacts to clicks yet — this is pure structure. A script, covered in the JavaScript course, would listen for clicks on #theme-toggle-btn, save the chosen theme with something like localStorage.setItem('theme', 'dark'), and update #theme-status and the page’s classes accordingly, so the choice is remembered the next time the visitor opens the page, even in a new tab or after restarting the browser.
This example is given as a full document because it shows the complete context — the <head> and a single script hook point in the <body> — that a real page needs before any storage logic is layered on top.
Example 2: A To-Do List Skeleton for localStorage
<form id="todo-form">
<label for="todo-input">New task</label>
<input type="text" id="todo-input" name="todo" required>
<button type="submit">Add Task</button>
</form>
<ul id="todo-list">
<!-- Saved tasks will be inserted here by JavaScript -->
</ul>
Result: The browser renders a labeled text field with an “Add Task” button, followed by an empty bulleted list. Visually nothing is stored yet — the list stays empty until a script runs. This markup is the target for a to-do app: on submit, a script would read the input, push it onto an array kept in localStorage as a JSON string, and re-render <li> items inside #todo-list — including on the next visit, since localStorage survives reloads.
Example 3: A Per-Tab Cart Counter for sessionStorage
<nav aria-label="Site">
<a href="/shop">Shop</a>
<a href="/cart">Cart (<span id="cart-count">0</span>)</a>
</nav>
Result: A navigation bar renders with two links, “Shop” and “Cart (0),” where the “0” sits inside its own <span>. Because a shopping session is usually meant to apply to one browsing tab rather than follow the visitor everywhere forever, this is a good fit for sessionStorage: a script would increment a count in sessionStorage each time an item is added and update the text inside #cart-count. Opening the shop in a second tab starts a fresh, empty cart in that tab, while closing the tab clears it entirely.
Under the Hood: How the Browser Handles Web Storage
It helps to walk through what actually happens, in order, when a page uses Web Storage:
- The browser requests the HTML document and begins parsing it top to bottom, building the DOM tree exactly as it does for any page — Web Storage plays no part in this step.
- When the parser reaches a
<script>element, or once the DOM is ready if the script is deferred, the script executes and can accesswindow.localStorageorwindow.sessionStorage, which the browser has already prepared for that origin before any script runs. - The script calls
getItem()to check for previously saved values, and uses standard DOM methods likedocument.getElementById()andtextContentto update elements already sitting in the DOM — which is exactly why those elements need stableidattributes in your HTML. - When the user interacts with the page, such as clicking a button or submitting a form, an event handler calls
setItem()to persist the new value. The browser writes it to disk forlocalStorage, or to memory tied to that tab forsessionStorage. - If another tab open to the same origin is also watching, the browser fires a
storageevent in that other tab wheneverlocalStoragechanges — this is how, for example, logging out in one tab can automatically log out every other open tab of the same site. - When the browser tab or window closes,
sessionStoragefor that tab is discarded.localStorageis untouched and will be there the next time any tab visits that origin.
None of this involves the network: unlike cookies, stored values are never automatically attached to HTTP requests, which is one reason Web Storage is both more efficient for large amounts of data and unsuitable for anything the server needs to see, like authentication tokens meant to be validated on every request.
Common Mistakes
Mistake 1: Duplicate IDs on Elements Meant to Be Storage Hooks
Storage-driven scripts almost always locate elements with document.getElementById(), which only ever returns the first match. Duplicate ids silently break this.
<p id="status">Preferences loading...</p>
<section>
<p id="status">Cart is empty</p>
</section>
Both paragraphs share the id status, which is invalid HTML because ids must be unique within a document, and it means a script targeting #status will only ever reach the first one, leaving the second permanently out of sync with storage. The fix is to give each element its own unique, descriptive id:
<p id="theme-status">Preferences loading...</p>
<section>
<p id="cart-status">Cart is empty</p>
</section>
Mistake 2: Treating Web Storage as a Safe Place for Sensitive Data
Because it is easy to reach from JavaScript, some pages are tempted to stash things like passwords or auth tokens in localStorage “for convenience.” Anything in Web Storage is plain text, readable by anyone with access to the browser’s developer tools, and readable by any script that runs on the page — including a malicious one injected through an XSS vulnerability. It is also never sent to the server automatically, so it cannot participate in normal authentication flows the way a secure cookie can.
<form id="login-form">
<label for="username">Username</label>
<input type="text" id="username" name="username">
<label for="password">Password</label>
<input type="text" id="password" name="password">
<button type="submit">Log In</button>
</form>
Beyond the storage concern, this markup has an HTML-level mistake too: the password field uses type="text", so the value is displayed in plain view as the user types. It should use type="password", and separately, a script should never mirror that value into localStorage:
<form id="login-form">
<label for="username">Username</label>
<input type="text" id="username" name="username" autocomplete="username">
<label for="password">Password</label>
<input type="password" id="password" name="password" autocomplete="current-password">
<button type="submit">Log In</button>
</form>
Credentials and tokens belong in memory or in cookies marked HttpOnly and Secure, set by the server — never in localStorage or sessionStorage.
Best Practices
- Give elements that a storage-driven script will read or update stable, unique, descriptive
idordata-*attributes; treat them as a contract between your HTML and the script layer. - Namespace your storage keys, for example
siteName-cartCount, so multiple scripts or third-party widgets on the same origin cannot accidentally overwrite each other’s data. - Choose
sessionStoragefor anything that should reasonably reset when a tab closes, such as draft form data or a one-time flash message, andlocalStorageonly for genuinely long-lived preferences. - Never store passwords, tokens, or other sensitive data in Web Storage; use secure,
HttpOnlycookies handled by the server instead. - Keep the amount of data small; Web Storage is meant for preferences and small application state, not as a substitute for a real database.
- Always provide sensible default content in your HTML, as in the examples above, so the page is still meaningful and readable even before any script runs or if JavaScript is disabled.
- Remember that Web Storage is scoped per origin, so a page cannot read another site’s stored data, and
http://andhttps://versions of the same host are treated as separate origins.
Practice Exercises
- Exercise 1: Build the HTML skeleton for a font-size preference control: a set of buttons labeled “Small,” “Medium,” and “Large,” plus a paragraph of sample text whose size a script would later change based on a saved preference. Give every element a unique, descriptive
id. - Exercise 2: Create the markup for a “recently viewed products” section: a heading and an empty
<ul>with an id that a script could use to insert up to five product names read back from storage. Decide whether this should uselocalStorageorsessionStorageand be ready to explain why. - Exercise 3: For each scenario below, decide whether
localStorageorsessionStorageis the better fit, and explain your reasoning in a sentence: (a) remembering a visitor’s preferred language across visits, (b) keeping an in-progress multi-step form’s answers only for the current tab, (c) showing a “welcome back” banner only once per browsing session.
Summary
- Web Storage is an HTML5 API that lets pages store key-value data directly in the browser, without sending it to a server on every request the way cookies do.
localStoragepersists until explicitly cleared and is shared across all tabs of the same origin;sessionStoragelasts only for one tab and is cleared when that tab closes.- Both store strings only, are accessed through JavaScript methods like
setItem,getItem,removeItem,clear,key, andlength, and are scoped per origin. - As an HTML author, your job is to give storage-driven elements stable, unique ids and to provide sensible default markup that still makes sense before any script runs.
- Never store sensitive data like passwords or auth tokens in Web Storage; it is plain text and accessible to any script on the page.
- Choose
sessionStoragefor short-lived, per-tab data andlocalStoragefor genuinely persistent preferences.
