HTML to JSX

Paste ordinary HTML and get back JSX, the near-identical markup a React component is written in. The fifty fiddly renames are done for you: className, htmlFor, style as an object, self-closed tags and camelCased SVG attributes.

How to use it

The conversion runs through the browser's own HTML parser rather than a set of regular expressions, so a tag you left unclosed is read the way a browser would read it.

  1. Paste the HTML A fragment or a whole page — a template you are porting, something from a designer, an example off the web.
  2. Optionally wrap it as a component The output becomes an exported function returning the markup, fragment-wrapped if there are several roots.
  3. Copy the JSX across The remaining work is the part that was always yours: wiring real handlers and state.
Full instructions

HTML

JSX

In depth

Porting markup from HTML to React without the busywork

Every attribute rename that JSX demands, why the converter uses the browser's own parser rather than regular expressions, what the component wrapper does, and where the real work begins after the conversion is done.

Screenshot: What actually changes between HTML and JSX
What actually changes between HTML and JSX as it appears when the page opens.

Why HTML does not paste straight into React

JSX looks like HTML, but it is JavaScript. That means a handful of words that HTML uses freely are reserved in JavaScript and must be spelled differently. class becomes className. for becomes htmlFor. tabindex becomes tabIndex. Each change is tiny. Across a full page there are dozens of them, and missing even one stops the build.

Beyond the reserved words, JSX has its own conventions. Inline styles are objects, not strings. Void elements like input and br must have a closing slash. SVG attributes that use dashes need camelCase instead. HTML comments break the parser entirely and must be rewritten as JavaScript block comments.

None of this is hard. All of it is boring. It is the kind of work where you make a mistake on the thirty-seventh attribute because the first thirty-six went fine and your attention wandered.

Pasting HTML in

The left panel is labelled HTML. Paste anything from a single div to a full page with a head and a body. The converter handles both. A fragment with no wrapper element is fine too.

Try an example fills the input with a piece of HTML that shows off several of the changes: a class attribute, an inline style, a void tag, and an SVG icon. Clear empties both sides.

The source does not have to be tidy. Most real-world HTML is copied from a working page, a template marketplace, or a designer's handoff. It arrives with inconsistent indentation, old-style attributes, and sometimes tags that are not properly closed. The converter accepts all of it because it relies on the browser's parser, not on the markup being perfect.

What changes in the output

The right panel is labelled JSX. As soon as there is HTML on the left, the converted output appears on the right. Here is what it touches.

Attribute renames. class to className, for to htmlFor, and every other name that clashes with a JavaScript keyword or that JSX spells differently. data-* and aria-* attributes keep their dashes because JSX makes an exception for them by design.

Style objects. An inline style like style="padding: 16px; background-color: red" becomes style={{ padding: '16px', backgroundColor: 'red' }}. Every CSS property is camelCased. Every value becomes a quoted string. CSS custom properties that start with two dashes are kept in their original form.

Self-closing void tags. HTML allows a bare input with no slash. JSX does not. The converter adds the slash to every void element.

SVG attributes. An icon pasted from a design tool comes with stroke-width, fill-rule, clip-path, and others that all need camelCase. Missing one of these in a large SVG is easy. The converter handles the full set.

Comments. HTML comments are not valid inside JSX. They are rewritten as {/* ... */} so the content is preserved and the syntax is correct.

Brace escaping. Curly braces mean interpolation in JSX. If the original HTML has literal braces in its text, they are wrapped so they display as characters rather than being read as code.

Event handlers become stubs

An HTML onclick="doThing()" is a string of code that the browser evaluates. JSX wants a function reference. The converter writes onClick={() => /* doThing() */} with the original call in a comment. Nothing is silently dropped, but nothing pretends to work either.

This is deliberate. An event handler that fires real side effects from an automatic conversion is dangerous. The stub tells you exactly where you need to come back and write the real behaviour yourself.

The same treatment applies to every inline handler: onchange, onsubmit, onmouseover, and so on. Each one is renamed to its camelCase form, and the old code sits inside the comment block waiting for you to decide what to do with it.

The component wrapper

Tick Wrap it in a component function and the output becomes an exported function that returns the markup. The function is named after the first meaningful element it finds, giving you a starting point for the component name. If the HTML has several root elements, they are wrapped in a React fragment.

This is a convenience for a common next step. Once the JSX is inside a function you can add props, state, and hooks without rearranging anything. If you are pasting into an existing component, leave the checkbox off and copy just the markup.

Parsed by the real HTML parser

The conversion does not use regular expressions to find and replace attributes. It feeds the input through the browser's own HTML parser, walks the resulting DOM tree, and writes the JSX from that.

This matters when the HTML is messy. An unclosed tag, a missing quote, or nested elements in the wrong order are things the browser handles silently every day. The parser reads them the way a browser would actually display them, so the JSX matches the real DOM, not the source you thought you wrote.

It also means that malformed HTML does not produce malformed JSX. The output compiles even when the input would not pass a validator, because the browser already cleaned it up on the way in.

Full pages with head and body

You can paste an entire HTML page. The head and body are both converted in order. Meta tags, link tags, and script tags all come through.

React apps rarely need the head markup. Most of that lives in an index.html or is managed by a framework. Delete the parts you do not need from the output. The converter does not guess which parts matter to you.

Script tags are passed through as JSX elements. They do not become executable behaviour in your component. If the original page loaded a library from a CDN, you will still need to install that library through your package manager and import it the React way.

SVG icons deserve a mention on their own

Pasting SVG markup is where the converter earns its keep most clearly. A single icon from a design tool might carry a dozen dashed attributes: stroke-width, stroke-linecap, fill-rule, clip-path, stroke-linejoin, and more. Each one must be camelCased for JSX, and forgetting just one triggers a console warning at best or a render failure at worst.

Paste the entire SVG element, and every attribute comes out correct. If you are building an icon library for a React project, this step alone saves the most tedious hour of the whole migration.

The real work starts after the paste

The converter finishes the mechanical part. What remains is the part that was always going to be a person's job.

Static strings that should be props need to be pulled out. Repeated sections need to become loops. Hardcoded data needs to move into state. Event handler stubs need real functions behind them. Styling that was inline may move to a CSS module or a styled component.

None of that can be automated, because it depends on what the component is supposed to do. The value of the converter is that it gets the spelling and syntax out of the way so you can think about behaviour from the start.

Saving and copying the result

Copy puts the JSX on your clipboard, ready to paste into your editor. Download saves it as a file. The file extension is .jsx so your editor picks up the right syntax colouring without you having to rename it.

The stats bar at the bottom shows how many changes were made. A high count means the HTML had a lot of attributes that needed renaming. A count of zero means the input was already valid JSX, which happens more often than you might expect with simple markup.

Your markup stays on your machine

The conversion happens inside your browser. The HTML is parsed locally and the JSX is built locally. No part of your markup is sent anywhere.

That means proprietary templates, client work under an NDA, and internal tools are all safe to paste. There is no server, no queue, and no log. The browser tab is the only place the content exists. Disconnect from the network and the page still converts, because there was never a remote step involved.

Help

What actually changes between HTML and JSX

The conversion runs through the browser's own HTML parser rather than a set of regular expressions, so a tag you left unclosed is read the way a browser would read it. Comments are rewritten as well, since JSX cannot carry the HTML kind. Paste a whole page and the afternoon of small edits happens in one pass.

Paste the HTML

A fragment or a whole page — a template you are porting, something from a designer, an example off the web.

Optionally wrap it as a component

The output becomes an exported function returning the markup, fragment-wrapped if there are several roots.

Copy the JSX across

The remaining work is the part that was always yours: wiring real handlers and state.

Parsed, not pattern-matched

The input goes through the browser’s real HTML parser, so malformed markup converts the way a browser would actually read it — unclosed tags and all. What you get is JSX for the DOM your HTML truly produces.

Good to know

Inline handlers become stubs

onclick="doThing()" is a string of code; JSX wants a function. The converter emits onClick={() => …} with the original call preserved in a comment, so nothing is silently lost and nothing pretends to work.

SVG pastes are the big win

Icons come with stroke-width, fill-rule, clip-path — all needing camelCase, all easy to miss one of. Paste the whole SVG here and the attributes are done.

Braces in text are escaped

{ and } mean interpolation in JSX, so literal ones in the text are wrapped as expressions. It looks odd and it is exactly right.

Common questions

Why did my attribute survive unchanged?
data-* and aria-* attributes keep their dashes in JSX by design — that is correct, not an omission. Unknown custom attributes also pass through untouched.
How are styles converted?
style="padding: 16px; background-color: red" becomes style={{ padding: '16px', backgroundColor: 'red' }} — properties camelCased, values as strings, custom properties quoted.
Does it handle a full page with head and body?
Yes — everything inside both is converted in order. React apps rarely want the head markup, so delete what you do not need from the output.
Is the output ready to run?
It compiles, but inline event handlers are stubs on purpose and script tags do not become behaviour. The conversion is the boring part; the interactivity is still a person’s job.