Markdown Editor + Preview
Write Markdown beside a sanitized live preview with scroll that stays in sync — entirely on this device.
Scrolling one pane moves the other to the matching block.
The rendered preview appears here as you type.
Mermaid fences render as code blocks here — the preview is sanitized house output.
Press ? for shortcuts
Do it offline:$
How to use it
- Type or paste into the editor pane, or drop a .md or .markdown file — the file is read here and no request leaves the page.
- Wrap the selection from the toolbar or its shortcuts: bold, italic, link, inline code, heading level, bullet list — and Tab indents inside a list item while Shift+Tab outdents it.
- Leave Synced scroll on so scrolling either pane brings the matching block into view on the other side, or send the view to Editor only or Preview only when one pane deserves the full width.
- Copy either side, download .md or .html, or share a permalink — the draft also waits here for your next visit unless it reads as credential-shaped.
Markdown is quick to write and awkward to check: the broken table shows up after you publish. This page sets a plain editor beside a live preview, keeps both scrolled to the same block, and leaves the draft in this browser. The preview is sanitized house-markdown output — one renderer, one cleaning pass, and every removal named in the row above the pane.
How it works
Every settled keystroke walks one path. The Markdown goes to the house renderer, which reads CommonMark 0.31.2 with the GitHub extensions and returns HTML plus a list of whatever it refused; that HTML passes through DOMPurify before the preview node is allowed to see it. This page has no raw-HTML passthrough switch at all, so the right pane is sanitized house-markdown output and nothing else. The conversion options themselves belong to the converter page and are not restated here.
Synced scroll is measurement rather than a percentage. The renderer labels the blocks it emits with the source line each one began on, so scrolling one pane looks up the nearest labelled block and aligns its twin on the other side. Headings, paragraphs, quotes, lists and tables carry a label; fenced code and horizontal rules do not, so alignment falls back to the labelled block above them rather than guessing. The two scroll listeners also take turns — whichever pane is being driven stays muted while it moves — which is why a tall preview beside a short source settles instead of oscillating.
Storage is local and conditional. The draft, the chosen view and the split ratio are written to this browser only, and the confirmation appears after the write succeeds rather than before it. When the text reads as credential-shaped — a bearer token, a PEM header, a password line in a pasted setup document — the draft is skipped entirely, because a shared machine is a bad place to leave one. A permalink carries the state in the URL fragment, which browsers never send to a server.
Size decides the schedule. Short documents re-render on the keystroke; past 64 KB the render waits for a brief pause in typing, and past 256 KB it moves to a worker with an elapsed counter and a Cancel button so the editor never stalls. Both panes hold the same box in every state — empty, typing, working, refused — including the notice row, which is present even on a document where nothing was removed.
Examples
# Release notes
- [x] ship batch 14
- [ ] tag
| Item | Done |
| --- | --- |
| Docs | yes |
```mermaid
graph TD; A-->B;
```
<h1>Release notes</h1>
<ul>
<li class="task-list-item"><input type="checkbox" disabled="" checked=""> ship batch 14</li>
<li class="task-list-item"><input type="checkbox" disabled=""> tag</li>
</ul>
<table>
<thead>
<tr>
<th>Item</th>
<th>Done</th>
</tr>
</thead>
<tbody>
<tr>
<td>Docs</td>
<td>yes</td>
</tr>
</tbody>
</table>
<pre><code class="language-mermaid">graph TD; A-->B;
</code></pre>
The sample the Sample button loads, and the state a permalink of it carries. Note the last block: a mermaid fence arrives as an ordinary code block tagged with its info string. Mermaid fences render as code blocks here — the fence text is kept exactly as you wrote it, and no diagram library is fetched by this page at any point.
# Notes
| Item | Done |
| --- | --- |
| Docs | yes |
- [x] ship W1
- [ ] tag release
See [the plan](javascript:alert(1)) and www.example.com[^1]
[^1]: Autolink demo.
<h1>Notes</h1>
<table>
<thead>
<tr>
<th>Item</th>
<th>Done</th>
</tr>
</thead>
<tbody>
<tr>
<td>Docs</td>
<td>yes</td>
</tr>
</tbody>
</table>
<ul>
<li class="task-list-item"><input type="checkbox" disabled="" checked=""> ship W1</li>
<li class="task-list-item"><input type="checkbox" disabled=""> tag release</li>
</ul>
<p>See [the plan](javascript:alert(1)) and <a href="http://www.example.com">www.example.com</a><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>Autolink demo. <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
A working document, and the reason the notice row is always there: this one reads "Removed during sanitization: 1 javascript: URL" while everything else — the table, the checkboxes, the bare www. host, the footnote — survives untouched. The refused link stays visible as the literal text you typed, so you can see what to fix and where.
<script>alert(1)</script>
text <b>bold</b>
<script>alert(1)</script>
<p>text <b>bold</b></p>
Hand-written HTML in a Markdown draft is shown, not obeyed: three segments here — the script block and the two bold tags — come back as visible characters, counted by a chip. There is no switch on this page that changes that answer, which is the point of writing prose in it.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
A fence outranks a hash
Lines starting with # inside a fenced block stay text, so the preview never grows a heading you did not write and shell transcripts paste in safely. A fence carries no line label either, which is why scrolling past a long snippet settles on the prose around it.
Unknown info strings survive as labels
Whatever word follows the opening fence becomes a language class on the code element and changes nothing else. No highlighter runs here, and no renderer is looked up for a fence tag the page does not know — an unrecognised label is kept, never guessed at.
Setext headings and lazy lines label from the top
An underlined title and a paragraph whose continuation lines sit flush left are each one block, so the line label points at where the block opened. Scrolling lands you at the start of the passage rather than in the middle of it.
Tight and loose lists differ in the preview
A blank line between items wraps each one in a paragraph, so the same bullets can render taller after an edit you barely noticed. Alignment tracks the list item, which keeps the target stable while you convert one form to the other.
Windows line endings count the same
CRLF input is normalised before anything is counted, so line numbers agree with what your desktop editor shows and a file saved on Windows previews exactly like its Unix twin.
Escaped pipes stay inside the cell
A backslash-escaped pipe in table content is a character, not a column boundary, and the preview shows it bare. Handy for documenting alternation or shell pipelines in a table.
An empty document is still a layout
Whitespace alone produces no blocks and therefore no preview content — both panes keep their full height, the hints keep their inset, and the notice row keeps its line. Nothing reflows when the first character arrives.
One enormous line is still just size
A quarter-megabyte paragraph with no newline in it renders like any other block; only the total length matters, and the schedule shifts to the worker on the same threshold as a thousand short lines.
Common errors
Input is larger than the 4 MB limit for this tool.- The only refusal the editor has. On screen it reads "This document is larger than the 4 MB limit for this editor. Your text is untouched — split it into smaller files." Nothing is truncated and the text you typed is left exactly as it was, so you can split the document and carry on; the limit counts characters, not bytes on disk.
passthrough off: 3 raw HTML segment(s) escaped — reported, non-blocking- Counted segments, not lines: an opening tag, a closing tag and a block each count once. It is a note rather than a failure — the draft keeps rendering, with the markup visible as text.
input contains an unpaired surrogate; it was encoded as U+FFFD- Half of an emoji or other astral character arrived without its partner, usually from a string cut mid-character upstream. The replacement character keeps the preview well-formed instead of failing the render.
FAQ
Can I export a PDF?
No, and that is deliberate: there is no PDF export — your browser's Print to PDF is the honest answer. Open the preview at full width and print the page; the result uses the fonts and page size your system already knows about, which a bundled generator would only approximate.
Does the preview draw Mermaid diagrams?
It does not. A mermaid fence stays a code block, and no diagram library is loaded on this route at all — that keeps the page small and predictable for writing. The fence keeps its info string, so the source is ready to paste wherever you do render diagrams.
Where does my draft live when I close the tab?
In this browser's local storage, on this device, and nowhere else. It comes back on your next visit, Clear removes it, and it is never written when the text looks credential-shaped. Typing before the page finishes loading also wins: your keystrokes are never replaced by a restored draft.
Is this the same HTML the converter page gives me?
Same renderer, same sanitizer, different job. This page shows you the rendered result while you write, with house defaults and no option row; the converter page is where the feature toggles, the copyable HTML source and the passthrough choice live. Copy HTML here gives you the sanitized markup behind the preview, minus the line markers this split view stamps on each block to keep the two sides aligned — the exported file is the markup printed in the examples above.
Why does the preview stop following near the end?
Because a preview is rarely as tall as its source. When the shorter pane runs out of scroll, alignment clamps at the last block instead of forcing the other pane past its own end — which is also what stops the two panes from pushing each other back and forth.
What happens if I drop a 6 MB file?
A file that decodes past the 4 MB character limit is refused with the size banner quoted under Common errors, and the editor keeps whatever was already in it — a rejected file never replaces your draft or your saved copy on the way to being turned down. Anything past roughly 12 MB is refused without being read at all, and files that sniff as binary are turned away before any decoding is attempted.
Can I edit the preview directly?
No. This is a Markdown editor, not a rich-text one: the left pane is the document and the right pane is a read-only render of it. Nothing you see in the preview can be dragged, and no hidden markup is ever written into your source behind your back.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Render the preview now, whatever the document size.
- ⌘/Ctrl + B
- Wrap the selection in bold markers.
- ⌘/Ctrl + I
- Wrap the selection in italic markers.
- ⌘/Ctrl + K
- Turn the selection into a link and leave the caret in the URL.
- ⌘/Ctrl + S
- Download the draft as .md.
- ⌘/Ctrl + C
- Copy the pane you are in when nothing is selected — the draft from the editor, the sanitized HTML from the preview.
- ⌘/Ctrl + Shift + C
- Copy that same pane's contents despite a selection.
- t
- Cycle the theme palette (idle while a text field has focus).
- ? or ⌘/Ctrl + /
- Open the shortcut overlay.
- Esc
- Close the topmost dialog; the draft always stays.