CSV to HTML Table
Turn CSV or TSV into a semantic, injection-safe HTML table — or paste a table back and get CSV — entirely on this device.
Pretty puts each tag on its own line, indented by the unit you pick. Unchecking removes the attribute only — header cells stay th.
Expand repeats a spanned value into every cell it covers and says how many. A byte-order mark makes Excel open the CSV as UTF-8.
The table renders here once there are rows.
Converted in this tab — nothing is uploaded. Press ? for shortcuts
Do it offline:$ pandoc table.csv -f csv -t html
How to use it
- Paste your rows, or drop a file on the left pane — it is read in this tab, and the badge above flips in one click.
- Say whether the first row is the header, then fill in a caption and a class if the target page wants those hooks.
- Pick Pretty with two spaces, four spaces or a tab, or Minified for one line; the preview redraws as you change the options.
- Copy the markup, save it as .html, or put the setup in a link — anything credential-shaped is left out of the buffer.
Paste rows and get the markup you would otherwise hand-write: a table element, an optional caption, a thead carrying th scope="col", then a tbody. Cell text, header text, the caption and the class value are escaped before they reach the output, so a value holding a tag stays text. Pretty or minified — and a pasted table converts back the other way.
How it works
A table element keeps its meaning in its parts. The caption names the whole table, thead marks the row of labels, tbody holds the data, and th scope="col" states that a header cell labels the column beneath it rather than the row beside it — which is what lets a screen reader announce “price, 1.50” instead of a bare number. Those elements are all this page writes.
Escaping happens once, on every string, on the way out. Five characters become character references — & to &, < to <, > to >, " to " and ' to ' — and that substitution is the whole boundary between data and document: a cell cannot open a tag, a caption cannot end an attribute. A chip counts the replacements.
Examples
item,qty,price
Widget & Co,3,1.50
<Bolt>,10,0.75<table>
<caption>Order lines</caption>
<thead>
<tr>
<th scope="col">item</th>
<th scope="col">qty</th>
<th scope="col">price</th>
</tr>
</thead>
<tbody>
<tr>
<td>Widget & Co</td>
<td>3</td>
<td>1.50</td>
</tr>
<tr>
<td><Bolt></td>
<td>10</td>
<td>0.75</td>
</tr>
</tbody>
</table>Three characters needed references: the ampersand in a company name and the angle brackets around a part code. The chip reads Escaped 3.
comment
<script>alert(1)</script>&"'<table>
<thead>
<tr>
<th scope="col">comment</th>
</tr>
</thead>
<tbody>
<tr>
<td><script>alert(1)</script>&"'</td>
</tr>
</tbody>
</table>The worst cell anyone will paste. It renders as literal text; the only < characters left are the ones this page wrote.
2024-01-05,shipped
2024-01-06,held<table><tbody><tr><td>2024-01-05</td><td>shipped</td></tr><tr><td>2024-01-06</td><td>held</td></tr></tbody></table>Header off means no thead and no th at all — two data rows in a tbody. Minified drops every newline; a browser reads the same table.
<table><caption>Q3</caption><thead><tr><th>region</th><th>total</th></tr></thead><tbody><tr><td>North & East</td><td>1,204</td></tr></tbody></table>region,total
North & East,"1,204"Going back the other way, th cells become the header row and & decodes to a plain ampersand. A caption labels the table rather than a row, so it is not carried across.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
An empty cell stays empty
A blank value emits <td></td>. A padding entity is content nobody typed, and it reads aloud.
A line break inside a cell survives as text
The break is kept inside the td and no br is inserted — HTML collapses it visually unless CSS says so.
An ampersand in your data is data
A cell already reading & comes out as &amp;: it arrived as text. Nothing is interpreted twice.
Dropping scope keeps the th
The checkbox removes only the attribute. Header cells stay th, so the row still reads as a header.
Caption and class share one escaper
There is no trusted field. A newline in class is refused outright: an attribute cannot hold one.
Wide and long tables convert anyway
Past 50 columns or 5,000 rows a warning says the layout will hurt, then the markup is emitted anyway.
Pretty and minified are the same table
Indentation is only whitespace between tags. Match the file you are pasting into, or match nothing.
Reading a table back keeps text, not markup
Bold and links go, their text kept and counted; a br becomes a real line break.
Merged cells stop the reverse direction
colspan or rowspan above 1 has no grid equivalent, so it refuses until you choose Expand.
Common errors
No <table> element found in the input — paste markup that contains a <table> element, or convert in the other direction.- The reverse direction reads table subtrees only. A styled div layout has nothing to walk, so it stops rather than invent a grid.
Line 1, column 12: this cell has colspan="2" — a CSV grid has no merged cells; choose Expand to repeat the value into every cell it covers, or remove the colspan.- One cell spanning two columns has no single row-and-column address. Expand copies the value into each covered cell and counts them.
Line 1, column 22: <td> opened here is never closed — malformed table markup is refused rather than guessed; close the tag and convert again.- Browsers close cells for you; this page will not, because the recovery it picked might not be the one you meant. The frame points at the opening tag.
Class attribute contains a line feed (U+000A) at position 6 — an HTML attribute value cannot carry line breaks or control characters; remove it, or separate class names with a single space.- Several class names are legal, separated by single spaces. A pasted newline is not, and deleting it quietly would change the hooks your stylesheet looks for.
FAQ
Is this markup safe to paste into a live page?
Yes for the five characters that matter in text nodes and double-quoted attributes — the whole surface this markup has. They leave as references, so no value can become an element. It is not a sanitizer for markup someone else handed you.
Why th scope="col" rather than a bolded first row?
Bold is a look; th plus scope is a relationship. Scope lets assistive technology repeat the label while moving down the column — the WCAG H63 technique. Uncheck it and the th elements stay: the spec still pairs a first-row th with its column, enough for a simple table.
Can I paste a table from a web page and get rows back?
Yes, when the markup closes its cells and rows. Attributes, comments, uppercase tags and inline formatting are fine, and the first table wins if there are several — but an unclosed cell is refused, not repaired.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Convert straight away instead of waiting for the debounce.
- ⌘/Ctrl + S
- Save the output — .html one way, .csv the other.
- ⌘/Ctrl + C
- With nothing selected, copy the whole output.
- ⌘/Ctrl + Shift + C
- Copy the whole output despite a selection.
- s
- Turn the conversion around; one undo puts it back.
- t
- Toggle light and dark.
- ? or ⌘/Ctrl + /
- List these bindings.
- Esc
- Close whichever overlay is on top.