GindaChen/live-html
live-html
edit HTML in the browser, save back to the file
A tiny library that makes any single-file HTML page a WYSIWYG editor — edits round-trip to disk via the File System Access API, no build step, no database, no framework. The rendered DOM is the source of truth.
v0.1 · MIT · single file install · works in Chrome/Edge today, falls back to download elsewhere
github: gindachen/live-html · this page is the demo: try the toolbar in the top-right

1 · the gap

every slide stack splits source-edit and rendered-view. nobody round-trips.


what slide frameworks do

slidev / reveal / marp / quarto

One-way pipelines: write Markdown or JSX, build, render. To fix a typo you spotted on the rendered slide you flip back to the editor and find the line. Hot reload makes this fast — it does not make it round-trip.

  • source file is the truth, render is a derivative
  • no edit on the artifact you actually look at
  • visual fiddling means leaving the page
what live-html does

edit the rendered DOM, write back to disk

Click a heading, retype it. CSS variable in the design-system slide? Drag the swatch. save writes a coalesced patch back to the original .html file. The artifact stays a single file. git diff shows what you changed.

HTML has had contenteditable for two decades and File System Access has shipped writes since 2020. Nothing here is novel — it is glue nobody bothered to ship.
contenteditable MDN · docs File System Access · MDN

2 · try it now

edit mode is already on. just click any text and start typing.


click this heading and retype it.

Select a word in this paragraph and type to overwrite. Press ⌘Z to undo, ⌘⇧Z to redo. The op-log counter in the toolbar ticks up with each change.

When you're done, click save. On Pages this downloads your edited copy; locally it writes back to the file you opened. Need to step out of edit mode? Toggle edit off in the toolbar.

three things to try

  • edit a heading on this slide, then arrow-key to slide 1 — your change is still there
  • open the log panel from the toolbar to see the structured op-log entries
  • jump to the design-system slide and drag a color — the deck recolors live

what won't break

  • arrow-key navigation pauses while you're typing in a contenteditable
  • refresh the page: ops persist in localStorage, the deck re-renders the way you left it
  • hit revert in the toolbar to drop unsaved ops and reload from disk
this is slide 3 · keep going arrow keys / spacebar advance edits stick across slides

3 · architecture in 60 seconds

three layers, ~one file, no dependencies


DOM (live)contenteditable input op log in localStorage { kind, path, before, after, ts } ↓ coalesce same-element <30sreplay against documentElement.outerHTML disk fileFile System Access (Chrome/Edge)download fallback (Safari/Firefox)
layer 1

contenteditable

Toggling edit mode flips contenteditable="true" on every node matching the configured selector. Native selection, IME, paste — untouched. We add a focus outline and that's it.

layer 2

op log

Each input becomes {kind, path, before, after, ts} and coalesces into the previous op if same-element within ~30s. Persisted to localStorage so a refresh keeps your work.

layer 3

file system

save replays the log against documentElement.outerHTML, requests a writable handle (cached after first grant), writes. No handle? Falls back to a download.

FSA spec · wicg.github.io caniuse · native-filesystem-api

4 · the op log

structured edits, coalesced, undoable, replayable


shape

{
  kind:   "content",
  path:   "section:2>div:1>p:0",
  before: "edit HTML in the browser",
  after:  "edit any HTML page in place",
  ts:     "2026-04-27T14:08:33.412Z"
}

two kinds today

  • content — text edits to a node addressed by structural path
  • theme — CSS variable overrides; saved into <style id="theme-overrides">

why path, not id

  • most authored HTML doesn't sprinkle ids on every paragraph
  • structural paths are stable while editing in place — you rarely reorder siblings mid-session
  • nodes that move trigger a re-anchor on next save; pathological reorders show up as a regenerated op set, not silent drift

coalescing rule

  • same opKey (path or token), same direction, within 30s ⇒ merge after-state, drop intermediate
  • typing a word doesn't make 8 ops; it makes 1
  • if the merged op cancels itself (before === after), it pops off the log

undo / redo

  • walks the array, not the browser undo stack
  • truncates forward log when you edit after an undo (familiar git/editor semantics)
  • head pointer separates applied from redoable
recipe · recipes/op-log.md storage key configurable per page

5 · save · the file is the truth

one file in, one file out, meaningful diffs


chrome / edge — write back

  • first save asks for a writable handle on the original file via showSaveFilePicker / createWritable
  • handle cached in IndexedDB; subsequent saves are silent
  • save = clear log + write disk

safari / firefox — download

  • same pipeline, last step swaps to <a download>
  • you replace the file by hand once; nothing else changes
  • this docs page runs in download mode (Pages is read-only)

theme overrides bake into one block

CSS-variable edits don't pile up as inline styles. Every theme op collapses into a single <style id="theme-overrides"> block at save time:

<style id="theme-overrides">
:root{
  --accent:  #f07fcc;
  --t-h1:    31px;
  --t-body:  15px;
}
</style>
One block, one diff, regardless of how many tokens you touched. git blame stays useful. Reverting a theme change is one line, not a regex sweep.
FSA showSaveFilePicker · MDN IndexedDB handle persistence · web.dev

6 · design system · why it matters

tokens are leverage; the next slide lets you pull on them


variables, not values

  • change --accent once → every h3, link, focus ring, and pill recolors
  • change --t-h3 once → every section heading in the deck rescales
  • the entire visual identity is ~16 tokens; the rest is plumbing

why this works in a live editor

  • tokens compose: a swatch picker is a one-line CSS variable write
  • the live preview is the page itself — no separate Storybook
  • theme ops are first-class citizens of the op log, not a side channel

so the next slide is...

...the live design-system editor for this deck. Click swatches, type into hex fields, bump the type ramp. Every change recolors every other slide instantly. Hit revert to drop them; hit save to bake the overrides into the file you ship.

Visitors to your deck become co-authors of its visual identity, with no commit access required. They get a downloadable copy; your repo is unchanged unless you choose to merge.

↓ press the right arrow, or click next in the bottom strip.

CSS custom properties · MDN

7 · design-system · live

this slide is the editor for this deck — every control mutates a CSS variable on :root and persists with save


colors — click swatch to pick

type ramp — affects every instance of the class

components — live preview using current tokens

.card

card heading

example body text inside a card.

.stat
42stat label
.spark
a "why exciting" callout block.
.pill
default ok warn acc

raw overrides — what gets baked into <style id="theme-overrides"> on save

no overrides · matches base design system

8 · install in 3 lines

paste these into any single-file HTML page


1 · in <head>
<link rel="stylesheet" href="editor.css">

Toolbar, history-panel, and edit-mode outline styles. Token-agnostic — inherits your theme.

2 · before </body>
<div class="ebar" id="ebar">
  <span class="lbl">edit</span>
  <button id="eb-toggle">off</button>
  <button id="eb-undo">↶</button>
  <button id="eb-redo">↷</button>
  <button id="eb-history">log</button>
  <button id="eb-save">save</button>
  <span class="dot" id="eb-status"></span>
</div>
<div class="hp" id="eb-hist-panel">...</div>

The toolbar markup. ids are the contract; the script wires them up.

3 · last thing in body
<script src="editor.js"></script>

Reads the optional <script type="application/json" id="live-html-config"> block, wires the toolbar, attaches the contenteditable layer.

recipes/template/ · the 3-line install as a runnable file config keys · storageKey, coalesceMs, saveMode, editableSelector

9 · examples

decks served from this same Pages site — live, editable, your edits download


add an example · docs/examples/

10 · the claude code skill

so the agent can author pages that come pre-wired to edit themselves


live-html ships as an installable Claude Code skill at ~/.claude/skills/live-html/. When Claude is asked to author a deck, dashboard, or status page, the skill triggers and the generated HTML drops in with the editor pre-wired.

what's in the skill

  • SKILL.md — trigger phrases, when to invoke, when not to
  • ARCHITECTURE.md — op-log, paths, save pipeline, theme bake
  • recipes/ — copy-paste templates per page shape (deck, dashboard, doc)
  • editor.css + editor.js — the artifact itself, vendored

why it matters

  • AI generates the first draft — usually 80% there, 20% off
  • without live-html, the user opens the file in an editor and re-prompts
  • with live-html, the user clicks the typo and retypes it; the file updates
  • the human-in-the-loop becomes a 1-second click instead of a context-restoring prompt
Authoring is now a two-phase loop: agent generates structure, human polishes content. The artifact is the same file; both ends co-edit it. No CMS, no API, no review queue.
install · cp -r live-html/skill ~/.claude/skills/live-html repo · live-html/skill

11 · when to NOT use this

be honest — round-trip-on-the-rendered-page is a narrow win


not for collaborative editing

No CRDTs, no presence, no merge. Two people editing the same file at the same time will overwrite each other on save. Use a real CMS or a Notion / HedgeDoc / Etherpad if you need shared cursors.

not for one-shot generated content

If the artifact is throwaway (a chat-rendered preview, a single-use report), the editing loop adds zero value. Render and discard.

not for review-gated changes

Click-to-edit is the opposite of "request approval, get review, merge". If your content needs a PR, use a PR. live-html is for the case where the author is also the reviewer.

not for live-data dashboards

Reactive dashboards reading from a websocket or polling an API are a different shape. live-html edits HTML, not data bindings. (Recipe: combine live-html for layout/labels with a separate fetch loop for data — works fine, just be deliberate.)

Sweet spot: single-author single-file artifacts where the rendered page is the deliverable. Decks, READMEs-as-pages, docs, status pages, CVs, landing pages.
compare · Notion (round-trip, no file) · Slidev (file, no round-trip)

12 · what's next

roadmap, ordered roughly by leverage


theme switching

  • named themes (light / dark / contrast / sepia) as first-class config
  • new op kind: theme-switch — swap the active theme name without rewriting tokens
  • users layer personal overrides on top of a chosen base

local co-edit server

  • tiny local server (~150 LOC) that brokers human + agent edits on the same file
  • agent posts ops; the human sees them appear in the toolbar log; either side can save
  • opt-in only — the default stays "no server, no install"

tiptap upgrade for structured edits

  • contenteditable handles plain text well; lists, links, code blocks need more
  • swap the editing layer to Tiptap when richer ops are needed; keep the op-log shape stable
  • still no build step — ship Tiptap as a single bundled file

.history/ snapshot dir

  • on each save, also write a timestamped snapshot to a sibling .history/
  • poor-man's local time-travel; meaningful when not under git
  • pruned by count + age, configurable
roadmap · ROADMAP.md open issues · github
fin
edit this page. save. diff. ship.
live-html is a small library you can read in one sitting. Open the source of this page — that's it.
github · gindachen/live-html install · git clone && open docs/index.html claude skill · ~/.claude/skills/live-html/ license · MIT
made with claude code · this deck is its own demo · press edit and prove it
edit clean
edit log · click an entry to jump to it ×
applied   undone save = download (Pages is read-only)
live-html · edit HTML in place
1 / 14