What changes between HTML and JSX
JSX looks like HTML but is really JavaScript, so a few rules differ. This converter parses your markup with the browser's own HTML parser (DOMParser), walks the resulting nodes, and prints each one back out in JSX form with two-space indentation. Because it uses a real parser instead of regular expressions, unclosed tags and missing quotes are repaired the same way a browser would repair them.
| HTML | JSX output | Reason |
|---|---|---|
class, for | className, htmlFor | Reserved words in JavaScript |
tabindex, colspan, maxlength, readonly, srcset and similar | tabIndex, colSpan, maxLength, readOnly, srcSet | React uses camelCase DOM property names |
stroke-width and other hyphenated names | strokeWidth | Hyphens are camelCased, except data-* and aria-*, which stay as they are |
style="margin-top: 4px" | style={{ marginTop: '4px' }} | React wants an object, not a string |
<br>, <img>, <input> and other void tags | Self-closed with /> | JSX requires every tag to close |
<!-- note --> | {/* note */} | HTML comments are not valid JSX |
xlink:href, xml:lang, xmlns:xlink | xlinkHref, xmlLang, xmlnsXlink | Namespaced attributes are camelCased |
onclick="go()", onmouseover="..." | onClick={() => { ... go() }}, onMouseOver={...} | React needs a function, not a string |
Worked example
Input:
<div class="card" style="background-color: red; margin-top:4px">
<label for="email">Email</label>
<input type="email" id="email" tabindex="1" disabled>
<!-- note -->
<button onclick="go()">Go</button>
</div>
Output:
<div className="card" style={{ backgroundColor: 'red', marginTop: '4px' }}>
<label htmlFor="email">Email</label>
{' '}
<input type="email" id="email" tabIndex="1" disabled />
{/* note */}
<button onClick={() => {
/* inline handler from HTML: check how it used this and event */
go()
}}>Go</button>
</div>
What you still have to fix by hand
- Event handlers need a review. An inline handler such as
onclick="go()"is wrapped in a function,onClick={() => { ... }}, so the JSX is valid, and the original code is kept inside it on its own lines, so even a handler that ends with a//comment cannot swallow the closing brace. Inline handlers ran withthisset to the element and aneventvariable in scope; in React the first is not set and the second is a parameter you add ((event) => ...). Replace the stub with a proper handler such asonClick={go}once you have moved the function into your component. - Spaces between inline elements are kept. Where the HTML has a space between text and an inline element (
Hello <b>world</b> again), the converter writes it as{' '}so it still renders. Whitespace between block elements, such as line breaks and indentation, is dropped, as it is in HTML. Text inside<pre>loses its line breaks and indentation, so wrap such text in a string yourself. - Special characters in text are escaped.
{,},<and>in text become{'{'},{'}'},{'<'}and{'>'}, and an entity-like text such as a literal&is protected. Entities such as are decoded by the parser and printed as the raw character. - Attribute values with double quotes are written as a JavaScript string,
title={"say \"hi\""}, so the line stays valid. - Framework attributes are removed. Attributes that cannot be written in JSX, such as
@click,:classorx-on:clickfrom Vue and Alpine, are left out of the output, and a note under the result lists each one so you can port it by hand. - Empty attributes. Only true boolean attributes (
disabled,checked,selected,readonly,required,multiple,hidden,openand similar) are written bare. An empty value on any other attribute, such asalt="", staysalt="". CSS custom properties in a style, such as--accent: 1, keep their name as a quoted key:style={{ '--accent': '1' }}. - Style and script contents. The contents of a
<style>block get their braces quoted one by one. Move such CSS into a stylesheet or a template literal.
Pasting a full document is fine: only the body content is converted, so the <html> and <body> tags are dropped, and anything the browser files under <head> (title, meta, link) is not converted. Ticking "Wrap in a component" puts the result inside export default function Component() with a fragment (<>...</>) around it, so sibling elements are allowed. Stray markup you do not want to carry over can be trimmed first with the HTML minifier.