System, light, and dark themes
InteractionLet readers follow their system preference or choose a light or dark appearance.
HTML + CSS + JavaScript
matchMedia("(prefers-color-scheme: dark)") Forms · Complete runnable example
Keep a short draft locally only after the reader chooses to save.
This preview runs in an isolated document. Clipboard writes and saved preferences may be blocked, and printing requires the standalone file. A dialog affects only the example. Reset reloads the original example state.
Use this pattern for short, nonsensitive notes on a permitted origin. The textarea works independently of storage. Opting in checks for an existing draft and asks whether to restore it or replace it with the current text.
The preview, copied text, and downloaded file use this same document.
<!doctype html>
<!--
MIT License
Copyright (c) 2026 MRHTML
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-->
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data: blob:; connect-src 'none'; form-action 'none'; object-src 'none'; base-uri 'none'"
/>
<title>A draft, on this device. | MRHTML example</title>
<style>
:root {
color-scheme: light;
--paper: #f5f3ee;
--surface: #fff;
--ink: #172a3b;
--muted: #536575;
--line: #d2dce1;
--blue: #234ed8;
--soft: #eef3ff;
--orange: #ad4b17;
--error: #a62440;
font:
16px/1.6 system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
sans-serif;
color: var(--ink);
background: var(--paper);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: clamp(24px, 6vw, 64px) 22px;
min-height: 100svh;
}
main {
width: min(760px, 100%);
margin: auto;
min-width: 0;
}
h1,
h2,
h3,
p {
margin: 0;
}
h1 {
font-size: clamp(2rem, 6vw, 3rem);
line-height: 1.08;
letter-spacing: -0.055em;
font-weight: 680;
max-width: 680px;
}
h2 {
font-size: 1.2rem;
line-height: 1.3;
letter-spacing: -0.02em;
}
p {
color: var(--muted);
}
header {
margin-bottom: 28px;
}
.brand {
font:
700 0.73rem/1.5 ui-monospace,
monospace;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--blue);
margin-bottom: 20px;
}
.intro {
max-width: 62ch;
margin-top: 16px;
}
.surface {
background: var(--surface);
border: 1px solid var(--line);
border-radius: 18px;
padding: clamp(20px, 4vw, 32px);
}
.note {
font-size: 0.85rem;
margin-top: 20px;
}
.eyebrow {
font:
600 0.72rem/1.5 ui-monospace,
monospace;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 20px;
}
button,
input,
textarea,
select {
font: inherit;
}
button {
border: 1px solid var(--ink);
background: var(--ink);
color: #fff;
border-radius: 8px;
padding: 10px 17px;
min-height: 44px;
font-weight: 650;
cursor: pointer;
}
button.secondary {
background: transparent;
color: var(--ink);
border-color: var(--line);
}
button:hover:not(:disabled) {
background: var(--blue);
border-color: var(--blue);
color: #fff;
}
button:disabled {
cursor: not-allowed;
opacity: 0.55;
}
label {
font-weight: 600;
}
input:not([type='checkbox']):not([type='range']):not([type='file']),
textarea,
select {
width: 100%;
min-width: 0;
border: 1px solid #8e9faa;
border-radius: 8px;
padding: 10px 12px;
background: #fff;
color: var(--ink);
}
textarea {
resize: vertical;
min-height: 180px;
}
input[type='range'] {
width: 100%;
accent-color: var(--blue);
}
input[type='checkbox'] {
width: 20px;
height: 20px;
accent-color: var(--blue);
}
:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 4px;
}
a {
color: var(--blue);
}
.status {
min-height: 1.6em;
font-size: 0.9rem;
margin-top: 18px;
overflow-wrap: anywhere;
}
.status[data-error='true'] {
color: var(--error);
}
[hidden] {
display: none !important;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.field {
display: grid;
gap: 7px;
margin-top: 16px;
}
footer {
font-size: 0.75rem;
color: var(--muted);
border-top: 1px solid var(--line);
margin-top: 30px;
padding-top: 16px;
display: flex;
justify-content: space-between;
gap: 12px;
}
noscript {
display: block;
padding: 14px;
background: #fff2d9;
border: 1px solid #ad4b17;
margin-top: 20px;
border-radius: 8px;
color: var(--ink);
}
@media (max-width: 400px) {
body {
padding: 25px 16px;
}
.surface {
padding: 20px;
}
button {
padding: 10px 14px;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
animation: none !important;
transition: none !important;
}
}
.draft-top {
display: flex;
justify-content: space-between;
gap: 15px;
}
.draft-top output {
font:
500 0.75rem ui-monospace,
monospace;
color: var(--muted);
}
#draft {
min-height: 220px;
line-height: 1.75;
background: #fbfcff;
}
.opt-in {
display: flex;
align-items: start;
gap: 10px;
padding-top: 22px;
margin-top: 22px;
border-top: 1px solid var(--line);
font-size: 0.9rem;
}
.opt-in input {
flex-shrink: 0;
margin: 3px 0 0;
}
#restore-choice {
background: var(--soft);
padding: 18px;
margin-top: 20px;
border-radius: 9px;
}
#restore-choice p {
font-size: 0.85rem;
}
</style>
</head>
<body>
<main>
<header>
<p class="brand">MRHTML / Runnable example</p>
<h1>A draft, on this device.</h1>
<p class="intro">
Write a short note. Saving is off until you explicitly choose to keep
a local draft.
</p>
</header>
<section class="surface">
<div class="draft-top">
<span class="eyebrow">Working note</span
><output id="character-count" for="draft">0 / 4000</output>
</div>
<div class="field">
<label class="sr-only" for="draft">Draft text</label
><textarea
id="draft"
maxlength="4000"
placeholder="What would you like to come back to?"
aria-describedby="draft-help"
></textarea>
</div>
<p id="draft-help" class="note">
Use this for nonsensitive notes. Local storage belongs to this browser
and site; it is not a backup or account sync.
</p>
<label class="opt-in"
><input id="autosave" type="checkbox" disabled /><span
>Keep a local draft on this device</span
></label
>
<div id="restore-choice" hidden>
<p>
A previous draft is available. Choose which text to keep before
autosave starts.
</p>
<div class="actions">
<button id="restore-draft" type="button">Restore saved draft</button
><button id="use-current" class="secondary" type="button">
Use current text
</button>
</div>
</div>
<div class="actions">
<button id="clear-draft" class="secondary" type="button" disabled>
Clear saved draft
</button>
</div>
<p id="draft-status" class="status" role="status" aria-atomic="true">
Autosave is off. Your current text exists only in this page.
</p>
</section>
<p class="note">
The embedded preview blocks local storage. Editing still works; use a
standalone copy on a permitted origin to try saving. Turning autosave
off keeps any previous saved draft until you clear it.
</p>
<noscript
>You can edit this note, but it is not saved. JavaScript is needed for
the optional local-draft controls.</noscript
>
<footer>
<span>MRHTML example</span><span>Original code · MIT</span>
</footer>
</main>
<script>
'use strict';
(() => {
const key = 'mrhtml-example-local-draft-v1',
draft = document.querySelector('#draft'),
opt = document.querySelector('#autosave'),
status = document.querySelector('#draft-status'),
choice = document.querySelector('#restore-choice'),
clear = document.querySelector('#clear-draft');
let timer = 0,
pending = null;
const report = (text, error = false) => {
status.textContent = text;
status.dataset.error = String(error);
};
const counter = () =>
(document.querySelector('#character-count').value =
`${draft.value.length} / 4000`);
function fail(text) {
clearTimeout(timer);
opt.checked = false;
pending = null;
choice.hidden = true;
report(text, true);
}
function save() {
clearTimeout(timer);
if (!opt.checked || pending) return;
try {
localStorage.setItem(
key,
JSON.stringify({
version: 1,
text: draft.value,
updatedAt: Date.now(),
}),
);
report('Saved locally on this device.');
} catch (error) {
fail(
error.name === 'QuotaExceededError'
? 'Not saved: local storage is full. Autosave is off; your text is still here.'
: 'Not saved: local storage is unavailable. Autosave is off; your text is still here.',
);
}
}
opt.disabled = false;
clear.disabled = false;
opt.addEventListener('change', () => {
clearTimeout(timer);
pending = null;
choice.hidden = true;
if (!opt.checked) {
report('Autosave is off. Any previous saved draft is kept.');
return;
}
let raw;
try {
raw = localStorage.getItem(key);
} catch {
fail(
'Local storage is unavailable here. Nothing was saved; your text is still here.',
);
return;
}
if (raw !== null) {
try {
const item = JSON.parse(raw);
if (
!item ||
item.version !== 1 ||
typeof item.text !== 'string' ||
item.text.length > 4000 ||
!Number.isFinite(item.updatedAt)
)
throw Error('invalid');
pending = item;
choice.hidden = false;
report(
'Previous draft found. Choose Restore or Use current text.',
);
} catch {
fail(
'Stored draft data could not be read. It was left untouched. Clear the saved draft to remove it.',
);
}
} else save();
});
draft.addEventListener('input', () => {
counter();
clearTimeout(timer);
if (opt.checked && !pending) {
report('Changes are not saved yet…');
timer = setTimeout(save, 500);
} else if (!opt.checked)
report('Autosave is off. Your edits exist only in this page.');
});
document
.querySelector('#restore-draft')
.addEventListener('click', () => {
if (!pending) return;
draft.value = pending.text;
pending = null;
choice.hidden = true;
counter();
report('Restored the local draft. Future edits will autosave.');
draft.focus();
});
document.querySelector('#use-current').addEventListener('click', () => {
pending = null;
choice.hidden = true;
save();
draft.focus();
});
clear.addEventListener('click', () => {
clearTimeout(timer);
opt.checked = false;
pending = null;
choice.hidden = true;
try {
localStorage.removeItem(key);
report(
'Saved draft cleared. Autosave is off; current text is unchanged.',
);
} catch {
report(
'Could not clear local storage. Autosave is off; current text is unchanged.',
true,
);
}
});
window.addEventListener('pagehide', () => {
if (opt.checked && !pending) save();
});
counter();
})();
</script>
</body>
</html>