HTML to JSX Converter
Paste HTML or SVG, get React JSX that renders exactly the same.
A snippet, a whole page or an SVG file (you can also drop a file here). Nothing you paste leaves this browser.
What changed
Check these
About the HTML to JSX Converter
Paste HTML or an SVG file and get JSX you can drop into a React component: class becomes className, for becomes htmlFor, style="…" becomes a style object, stroke-width becomes strokeWidth, void elements are self-closed and comments become {/* … */}. Attribute names come from React DOM's own table of HTML and SVG attributes, so tabindex, viewbox or xlink:href get the exact spelling React expects.
The converter also keeps what the page looks like. JSX throws away whitespace that contains a line break, so a space HTML shows between two links on separate lines is written {' '}, and <pre> text becomes string literals so its spaces survive. The markup is laid out the way Prettier formats JSX (the code of event handlers is kept as written). Everything runs in your browser.
How to use it
- Paste HTML or SVG into the box, open an .html or .svg file, or press Load sample. The JSX updates as you type.
- Choose JSX or TSX, and tick Wrap in a component to get
export default function Name() { return (…); }. For icons, tick Spread props so<Icon className="…" />reaches the root<svg>. - Read the notes under the result: they list everything that was renamed or left out, with the line of your input, and say what to change by hand (for example
thisin an inline handler). - Copy the code or download it as a .jsx or .tsx file.
Examples
<label for="email">Email</label> <input type="email" id="email" value="[email protected]" required tabindex="1">
<>
<label htmlFor="email">Email</label>{' '}
<input type="email" id="email" defaultValue="[email protected]" required tabIndex={1} />
</>value is the field's starting value in HTML, which React calls defaultValue (a value without onChange would make the field read-only). The {' '} keeps the space HTML shows between the label and the field.
<button style="font-size: 14px; z-index: 2" onclick="save()">Save</button>
<button style={{ fontSize: '14px', zIndex: 2 }} onClick={() => save()}>
Save
</button>Properties are camelCased; z-index takes a plain number because React adds no px to it, while 14px stays a string.
<svg viewBox="0 0 24 24" stroke-width="2" class="icon"><path d="M5 12h14"/></svg>
export default function Icon(props) {
return (
<svg viewBox="0 0 24 24" strokeWidth="2" className="icon" {...props}>
<path d="M5 12h14" />
</svg>
);
}With Wrap in a component, component name Icon and Spread props. In TSX the props are typed ComponentProps<'svg'>.
Common uses
- Moving a landing page, email template or Bootstrap/Tailwind snippet into a React, Next.js or Remix component.
- Turning SVG icons exported from Figma, Illustrator or Inkscape into icon components (editor metadata is removed).
- Converting markup from a CMS or a design handoff without fixing every attribute by hand.
What changes
- Attributes: renamed with React DOM's attribute table —
class→className,for→htmlFor,tabindex→tabIndex,readonly→readOnly,charset→charSet, SVGfill-rule→fillRule,xlink:href→xlinkHref,xml:space→xmlSpace.data-*andaria-*stay as they are. - Booleans:
disabled="disabled"becomesdisabled; an attribute without a value that is not a boolean, such asdata-open, becomesdata-open=""(in JSX a bare name meanstrue). - Numbers:
tabindex,colspan,rowspan,maxlengthand similar integer attributes become{2}. - Styles:
stylebecomes an object with camelCased names (-webkit-→Webkit,-ms-→ms, custom properties stay quoted as'--brand'). Numbers keep their units as strings, except0and the unitless properties React lists (zIndex,opacity,lineHeight,flexGrow…).!importantcannot be expressed and is reported. - Event handlers:
onclick="save()"becomesonClick={() => save()}and statements get a block. A leftoverjavascript:at the start is removed (in handler code it is only a label), a comma expression is put in parentheses so nothing after the comma runs while React renders, and a//comment gets its own line end so it cannot hide the closing braces. React has no props for window events such asonbeforeunload; those are reported. - Form fields:
value→defaultValue,checked→defaultChecked,<option selected>→defaultValueon the<select>, and<textarea>content →defaultValue. Inputs whose value is fixed (checkbox, radio, submit, hidden) keepvalue. - Structure: end tags HTML lets you leave out (
</li>,</p>,</td>) are added where the browser would close them,<tbody>is added around table rows,<br>becomes<br />, and<div/>is read as an empty element. - Style and script elements: the content of
<style>and<script>goes intodangerouslySetInnerHTML, because React escapes text inside them when it renders on the server (a > bwould becomea > b).
Why {' '} appears
HTML turns any run of spaces and line breaks into one space, so <a>Home</a>⏎<a>About</a> shows "Home About". JSX instead removes whitespace that contains a line break, so the same lines in JSX show "HomeAbout". The converter writes {' '} wherever HTML would have shown such a space between inline content, and drops whitespace that never shows (between block elements such as <div> and <p>, around <br>, at the start and end of a block). Spaces on the same line need nothing: JSX keeps them.
If your CSS turns those elements into blocks or flex items, the spaces do not matter: untick Keep spaces between inline elements to drop them, as most converters do.
Checked against a real JSX compiler
The test suite compiles the output with sucrase (which follows Babel's JSX whitespace rules) and compares the text React would render with the text a browser shows for the original HTML — for hand-written cases and for 300 generated snippets of mixed inline and block markup, at two line widths. Every output is also parsed with TypeScript's JSX parser.
Limitations
- Inline event handler code is otherwise copied as written, so a mistake in it stays (the notes flag unbalanced brackets and unclosed quotes where they can tell). Code that relies on
this(the element in HTML) orreturn false(cancelling the event in HTML) needsevent.currentTargetandevent.preventDefault()in React; the notes point these out. - React never runs
<script>content it renders; scripts are kept so you can see them, but should move into effects or modules. - Template syntax from Vue, Alpine or Angular (
@click,:class,*ngIf) is not HTML and is left out — rewrite it with React state. - Elements and attributes with namespaces that JSX cannot express (
inkscape:label, Word's<o:p>) are removed. - Whitespace is judged by the default display of each element; CSS that changes
displayorwhite-spacecan make a kept or dropped space matter differently.
Privacy
Everything happens in your browser. What you enter or open here is not uploaded or stored by MySmartCoPilot.
Frequently asked questions
Why did value become defaultValue?
In HTML, value on an input is only the starting text. In React, value makes the field controlled: without an onChange handler it can no longer be edited. defaultValue (and defaultChecked for checkboxes) keeps the HTML behaviour. If you want a controlled field, rename it back to value and add state and an onChange handler.
Do I need to import React?
Not with the JSX transform used since React 17 (Next.js, Vite, Create React App 4+ and current Babel and TypeScript setups). With an older setup that uses React.createElement, add import React from 'react'; at the top. The TSX output with spread props imports the ComponentProps type only.
Is my HTML uploaded?
No. The conversion runs entirely in your browser and works offline once the page has loaded; nothing you paste or open is sent anywhere.
Why are some character references turned into characters?
JSX understands only the 253 HTML 4 references such as © or ; newer HTML5 names such as ✓ would show up literally. The converter writes such characters directly (✓), and keeps invisible ones (non-breaking and zero-width spaces, soft hyphens) as references so you can see them. <, > and braces are escaped so the JSX still parses.
Can it convert a whole web page?
Yes. With Only the <body> content ticked (the default) a full page becomes the JSX of its body, and the notes list what was in <head>. Untick it to convert <html>, <head> and <body> too, for example for a root layout.