HTML to JSX converter
Paste HTML and get JSX that compiles. Attributes renamed, inline styles turned into objects, every element closed, and anything that cannot be converted automatically is reported rather than guessed.
What changes, and why
JSX looks like HTML and is not HTML. It compiles to JavaScript function calls, which means attribute names have to be valid JavaScript property names and every element has to be explicitly closed. Pasting raw HTML into a React component fails on all of that at once.
| HTML | JSX | Because |
|---|---|---|
class | className | class is a reserved word in JavaScript |
for | htmlFor | So is for |
tabindex | tabIndex | DOM properties are camelCase |
style="color:red" | style={{ color: 'red' }} | JSX takes an object, not a string |
<img src="a.jpg"> | <img src="a.jpg" /> | Every element must close |
<!-- note --> | {/* note */} | HTML comments are not JSX syntax |
disabled | disabled | Boolean attributes are already valid |
onclick="fn()" | removed | JSX needs a function reference, not a string |
Roughly sixty attributes get renamed, including the SVG ones — stroke-width becomes
strokeWidth, clip-path becomes clipPath — which is the part people
most often miss when converting an icon by hand.
Inline styles become objects
This is the conversion that takes longest by hand and the one most worth automating. Property names are camelCased, values become strings, and unitless numbers stay numbers.
<p style="color:#101b2d;font-weight:600;margin-top:12px">£58</p>
<p style={{ color: '#101b2d', fontWeight: 600, marginTop: '12px' }}>
£58
</p>
Custom properties keep their dashes and get quoted, because --brand is not a valid JavaScript
identifier: style={{ '--brand': '#3B4FE4' }}.
What it deliberately does not do
Three things are removed rather than guessed at, and each is listed underneath the panels when it happens.
Inline event handlers. onclick="addToCart(7)" is a string of JavaScript.
JSX needs onClick={() => addToCart(7)} — a real function, in scope, in your component. Only
you know where that function lives, so the attribute is dropped and the removal reported.
Script and style blocks. A <script> inside JSX does not run the way
you expect, and a <style> block belongs in a stylesheet or a CSS-in-JS call. Both are
removed with a note.
The doctype and document structure. A component returns an element, not a document.
<!DOCTYPE> is dropped. <html> and <body> are kept if
you paste them, but you almost certainly want to paste only the fragment you are turning into a component.
Component wrapper and fragments
With Wrap in a component ticked you get a complete file you can save and import. If your
HTML has more than one top-level element, the output is wrapped in a fragment — <>...</>
— because a React component must return a single node.
export default function Component() {
return (
<div className="card" data-id="7">
{/* product tile */}
<img src="a.jpg" alt="Linen shirt" />
<h3 className="title">Linen shirt</h3>
</div>
);
}
Untick it when you want a fragment to drop into a component you already have.
After the conversion
Four things to do by hand, in order of how often they bite:
- Reattach event handlers. Every removed
onclick,onsubmitandonchangeneeds a real function. - Make form fields controlled. React warns about
valuewithoutonChange, andselectedon an<option>should becomevalueplusdefaultValueon the<select>. - Rename to PascalCase if you want a component. A lowercase tag name compiles to a DOM element; only a capitalised one is treated as a component reference.
- Check text containing braces. Literal
{and}in text are escaped for you, but if you meant them as expressions you will want to rewrite them.
Does this work for Preact, Solid or Vue?
Preact — yes, entirely. It accepts both class and className, so
the output works as-is.
Solid — mostly. Solid uses class rather than className and
takes a style object like React does, so you will need to rename that one attribute back.
Vue — no. Vue templates are HTML, so you do not need converting at all. Paste your
markup straight into a <template> block.
Svelte — no. Svelte components use plain HTML with class and string styles.
Use your original markup.
Questions about this tool
Why was my onclick attribute removed?
Because JSX cannot use it. onclick="fn()" is a string that the browser evaluates; JSX needs onClick={{fn}}, a reference to a function that exists in your component's scope. There is no way to generate that automatically, so the attribute is dropped and the removal is listed under the panels.
Does it handle SVG?
Yes, and this is where it saves the most time. All the hyphenated SVG attributes — stroke-width, stroke-linecap, fill-rule, clip-path, stop-color — are camelCased, and self-closing tags are preserved. Converting an icon set by hand is exactly the job worth avoiding.
Can I convert a whole page?
You can, and you probably should not. A page is not a component. Convert the fragments — a card, a nav, a form — one at a time, which is how you would want to structure the React version anyway.
What about Tailwind classes?
They pass through untouched inside className. Tailwind works the same in JSX as in HTML; only the attribute name changes.
Is this the same as an HTML to React converter?
Same thing, different name. JSX is the syntax React uses, so converting HTML to JSX is converting it to React.
Related tools
HTML Formatter
Indent and structure any HTML file.
OpenHTML Beautifier
Turn minified HTML back into readable code.
OpenHTML Pretty Printer
Pretty-print HTML with the indent you want.
OpenHTML Minifier
Strip whitespace and comments from HTML.
OpenHTML Validator
Find unclosed tags and broken nesting.
OpenHTML Code Cleaner
Tidy exports from CMS and email builders.
OpenLearn the why, not just the how
Longer reading on formatting, indentation and minification.