HTML to JSX Converter
Turn HTML into a React component or a Pug template — attributes mapped to React props, entirely on this device.
A name that is not a legal identifier is turned into PascalCase, and an empty one becomes HtmlComponent; the notes under the panes say which name was emitted. A component returns one node: with more than one at the top level, a fragment adds nothing to the page, while a div adds a real element. One top-level element is returned as it is. Spread props puts {...props} on the root element, so the caller can pass className, id and event handlers straight through; a fragment takes no props, so it is skipped there. onclick and every other on* attribute is reported in the notes and never emitted, and a <script> body is reported and never emitted — paste the handlers back in yourself. value or checked on an input with no handler makes it a controlled field that React will warn about; add onChange, or switch to defaultValue and defaultChecked.
- required on <input> at line 2 became required={true} — in HTML the presence of a boolean attribute is the value, so the text after "=" carries no meaning.
Press ? for shortcuts · Stored only on this device —
Do it offline:$
How to use it
- Paste your markup, or drop an .html file onto the left pane.
- Choose the output: a React component, or a Pug template of the same tree.
- Name the component, pick TypeScript or JavaScript, then set the wrapper, spread and indent.
- Read the notes under the panes, then copy or save the result.
Paste page markup and take away a React component you can import, or the same tree as a Pug template. Names that clash with JavaScript keywords are renamed, valueless boolean attributes become explicit, an inline style turns into an object, and whatever was left behind is listed under the panes. It runs in this tab, and nothing is uploaded.
How it works
JSX looks like markup but assigns DOM properties, and two HTML attribute names are JavaScript keywords: class and for. The DOM settled that long ago by naming the properties className and htmlFor, and JSX follows the DOM — which is why tabindex, colspan, http-equiv and readonly also arrive with property spellings. Only names in an explicit table are renamed; an unfamiliar one rides through untouched with a note rather than a guess, while data- and aria- names stay as typed. A valueless attribute such as disabled becomes {true}: in HTML its presence is the value.
Pug describes that same tree with indentation instead of closing tags, so the emitter prints one line per element, two spaces deeper per level. A class or id value collapses into the .name and #name shorthand when it is safe to; a value carrying spaces or punctuation Pug reads as syntax goes into the bracketed attribute list, with a note. Multi-line text becomes a piped block. That template is emitted and structurally checked here — pug is not installed in this project and is never run.
Examples
<div class="card"><h2 class="card__title">Pricing</h2><p>From <b>$9</b> a month. <a href="/plans" target="_blank" rel="noopener">Compare plans</a></p></div>import type { ComponentProps } from "react";
export function HtmlComponent(props: ComponentProps<"div">) {
return (
<div className="card" {...props}>
<h2 className="card__title">Pricing</h2>
<p>From <b>$9</b> a month. <a href="/plans" target="_blank" rel="noopener">Compare plans</a></p>
</div>
);
}
export default HtmlComponent;
Defaults, single root: class takes the property spelling, the spread lands last so a caller can override anything, and the typed props line names the root tag. Text mixed with inline tags stays on one line, since a newline there changes the rendering.
<form action="/subscribe" method="post"><label class="lbl" for="email">Email</label><input id="email" name="email" type="email" required maxlength="120" autocomplete="email" onchange="validate()"><button class="btn" disabled>Join</button></form>export function SubscribeForm(props) {
return (
<form action="/subscribe" method="post" {...props}>
<label className="lbl" htmlFor="email">Email</label>
<input id="email" name="email" type="email" required={true} maxLength={120} autoComplete="email" />
<button className="btn" disabled={true}>Join</button>
</form>
);
}
export default SubscribeForm;
Four-space indent, JavaScript target: for becomes htmlFor, the length limit is emitted as a number because React types it as one, the valueless attributes become {true}, and the inline validator is reported and gone.
<section class="hero" id="top"><h1>Ship faster</h1><p class="lede">Two lines
of copy.</p><img src="/a.png" alt="Chart"></section>section.hero#top
h1 Ship faster
p.lede
| Two lines
| of copy.
img(src="/a.png", alt="Chart")
The same markup as a template: shorthand carries the section's class and id, depth replaces the closing tags, and the two-line paragraph becomes a piped block. Component name, language and wrapper are React-only settings.
<!DOCTYPE html><!-- pricing --><table cellpadding="4" style="border-collapse:collapse;--brand:#0ea5e9"><tr><td data-col="1" aria-label="Cost">$9 per {seat}</td></tr></table>export function HtmlComponent() {
return (
<>
{/* pricing */}
<table cellPadding="4" style={{ borderCollapse: "collapse", "--brand": "#0ea5e9" }}>
<tr>
<td data-col="1" aria-label="Cost">$9 per {"{"}seat{"}"}</td>
</tr>
</table>
</>
);
}
export default HtmlComponent;
Seven notes at once: the doctype goes, the comment survives in braces, an obsolete table attribute is renamed and flagged, the custom property keeps its name, and braces in the cell text are escaped so they render instead of compiling.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Handlers and script bodies are reported, never emitted
An onclick, an onsubmit or a script body is named in the notes with its line and then omitted. Re-attach the behaviour yourself.
value or checked without a handler gives React something to complain about
Markup treats those as starting values; React treats them as the field's value while it renders. No handler is written for you, so the note points at defaultValue or readOnly.
Forgiving parsing is where conversion turns lossy
Browsers repair markup silently: a paragraph closes when a block starts, a stray end tag is ignored, a repeated attribute keeps its first copy. The repaired tree is what gets converted, and each repair is named.
The React output goes through a compiler before it is printed here
Every example above is compiled by the TypeScript compiler in our tests. The Pug branch has no compiler available and is checked structurally instead.
Common errors
Input is larger than the 1 MB limit for this tool.- Past a megabyte the paste is declined whole rather than cut short, because half a component is worse than none.
Event handler onchange on <input> at line 1 was left out — this tool never emits script. Re-attach it as a React onChange handler.- A note, not a failure: the element is in your component, minus that attribute. React binds handlers to functions rather than strings of code.
<input> at line 1 carries value or checked, and this tool never emits handlers — React will warn about a controlled field with no onChange. Add onChange, or switch to defaultValue / defaultChecked, or add readOnly.- The component is emitted and runs; the warning arrives in your console, not here. Which fix is right depends on whether the field is meant to be driven by state.
my-widget-flag on <div> at line 1 is not in the JSX attribute table — it is passed through unchanged, so React renders it but TypeScript will not know it.- The table holds only names checked against React and its shipped typings. Anything else is copied exactly rather than camelCased on a hunch, since a wrong guess stops it reaching the element at all.
`</div>` closes an element with `<span>` still open at line 1, column 13 — `<span>` is treated as ending here — no bytes are moved.- The tokenizer's own wording, forwarded untouched. Overlapping tags cannot be represented, so the inner element closes there and the conversion carries on.
FAQ
Why is class renamed but data-state left alone?
class is a reserved word in JavaScript, so the DOM property has always been className, and JSX uses property names. data and aria attributes have no property twin: React writes them as spelled, so renaming breaks them.
Can I get a Pug file I can compile straight away?
You get template text mirroring your tree line for line. It has not been through the pug compiler, and carries no includes, mixins or extends — add those in your own build.
Does this handle a whole saved page, head and all?
Up to a megabyte, yes. The doctype is dropped with a note, since a component is a page fragment rather than a document, and script bodies never come across.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Run the conversion again with the options as they stand.
- ⌘/Ctrl + S
- Save the result, ahead of the browser's save-page action.
- ⌘/Ctrl + C
- Copies the result when nothing is selected.
- ⌘/Ctrl + Shift + C
- Copy the result whatever is selected.
- t
- Change theme, ignored while a text field is active.
- ? or ⌘/Ctrl + /
- Open the dialog listing these bindings.
- Esc
- Close that dialog.