How Edit Markdown Table is built
Edit Markdown Table shows a GitHub-Flavored Markdown table as an editable grid, and keeps the underlying |-delimited syntax perfectly column-aligned no matter what you do to it. Unlike Markdown Viewer, which renders a whole document read-only, this tool does one narrower thing — edit a table — and does it without ever making you hand-align a pipe again. This post covers the parser/serializer at its core and a couple of decisions that took more thought than they look like they should.
Tech used
A table parser with no dependency
GFM table syntax is small enough to not need a library: a header row, a separator row (---, :---, :---:, or ---: per column, marking left/center/right alignment), then zero or more data rows, all |-delimited. The parser reads the header row to fix the column count, then processes each data row against it — a row with fewer cells than the header is padded with empty cells, and a row with more has the extras dropped, matching how GitHub’s own renderer handles a ragged row rather than crashing on it.
One escape sequence, proven sufficient
A literal | typed into a cell has to become \| in the serialized Markdown, or it would be read back as a column boundary. The parser reverses that on import. The question that took real thought was whether \\ (a literal backslash) also needed special-casing, since \| is technically an escaped pipe. Working through it by hand: if a cell’s raw text already contains \| as two literal characters (not an escaped pipe), escaping only |→\| on serialize and unescaping only \|→| on parse is exactly reversible — round-tripping a cell through serialize-then-parse reproduces the original text. Adding a second rule to also unescape \\→\ would not be reversible for a cell containing a literal double backslash, because there’d be no way to tell, on the way back, whether a \\ in the source was meant to survive as-is or came from an escaped single backslash. The simpler, single-rule scheme turned out to be the provably correct one, not just the easier one to write.
A grid that’s a real <table>, not a <div> soup
The grid is a native <table> with explicit role="grid"/"row"/"columnheader"/"rowheader"/"gridcell", and each cell is a real <input> — which means Tab order between cells comes from the browser for free, on top of which Arrow-key and Enter navigation are layered for spreadsheet-style movement. The alternative considered was a full ARIA Authoring Practices Guide roving-tabindex pattern (one shared tab stop, arrow keys move a virtual focus). That pattern exists to solve a problem — too many individually-focusable elements breaking natural Tab flow — that a real <table> of real <input>s doesn’t have in the first place. Building it anyway would have added real complexity for no accessibility gain, verified by running the actual grid through axe-core and a dedicated keyboard-navigation test rather than assuming either design would pass.
Implementation & operational notes
No Web Worker. Parsing and serializing a table is synchronous string manipulation with no decode/encode step heavy enough to justify moving off the main thread — the same reasoning CSV Viewer uses for the same kind of work.
A cell can’t contain a line break. GFM table cells are single-line by design, so a pasted line break inside a cell is replaced with a space rather than being allowed to silently break the row structure on the next round-trip.
Pasting in an existing table is the second import path, alongside starting from a blank 3-column grid — paste GFM Markdown into the import box and it’s parsed straight into editable cells, including recovery from the ragged-row case described above.