DevKitHub

Programming

HTML to JSX Converter — Paste HTML, Get React JSX

Paste HTML to get JSX you can drop into a React component. Attributes are renamed from React’s own tables, style strings become objects, and anything that cannot be converted, such as an onclick string, is removed and named.

7 lines
9 lines
  • Removed an inline event handler, because JSX needs a function rather than a string of code: onsubmit on <form> at line 1 (onSubmit). Add it back as a function.
  • value became defaultValue on <input> at line 3: React treats a value without onChange as a read-only field, and warns.
  • checked became defaultChecked on <input> at line 4: React treats checked without onChange as a read-only field, and warns.

This tool runs entirely in your browser. Your input is never uploaded, stored or logged.

How it works

The input is parsed the way a browser parses it rather than searched and replaced, because HTML lets you leave out things JSX requires. The end tags of <li>, <p>, <td>, <tr> and <option> are optional, a browser inserts a <tbody> into a table that has none, and void elements such as <br> and <input> never close. Each of those is resolved into the tree first — the <tbody> included, since without it React warns and a server-rendered page fails to hydrate. Anything genuinely broken, such as a <div> that is never closed or tags closed in the wrong order, is reported with its line and column instead of being guessed at. The contents of <script>, <style> and <textarea> are read as text, so a "<" in code cannot open a phantom element.

Attribute names come from the table React itself uses when it warns "Invalid DOM property": class becomes className, for becomes htmlFor, tabindex becomes tabIndex and stroke-width becomes strokeWidth, while data-* and aria-* pass through untouched. Values need converting too, and this is where copied HTML breaks quietly. A style string makes React throw, so it becomes an object with camelCased keys whose values stay strings, because React adds px to a bare number. React reads disabled="" as false, so boolean attributes become bare props. A value or checked on an input would make it read-only, so they become defaultValue and defaultChecked, as do a textarea’s text and an option’s selected. Inline handlers such as onclick cannot become functions, so each is removed and named in a warning.

JSX removes a line break and the indentation around it wherever it sits next to a tag, which is why reformatted HTML loses the space between two links. Spaces are kept where a browser renders them — between inline elements and within text — and dropped beside block-level elements and inside tables, where React warns about whitespace text. A kept space that falls at a line break is written as {' '}. Text is escaped for JSX: braces become {'{'} and {'}'}, and entities stay as written, because JSX decodes the HTML 4 set. It does not decode HTML5 names such as &check;, a hex reference with a capital X, or &copy without its semicolon; each of those is fixed or reported. Inside <pre>, every space and line break is kept.

Common problems

Every example below is run against this tool in our test suite, so what it says here is what the tool actually does.

Tag <div> opened on line 2 was never closed.

<main>
  <div class="card">
    <p>Unclosed card
Why:
A browser closes every open element at the end of the document without complaint, so HTML with a missing end tag still renders. JSX has no such rule, and guessing where the element was meant to end would move content into the wrong parent.
Fix:
Add the missing end tag. Tags HTML allows you to omit — </li>, </p>, </td>, </tr>, </option> — are closed for you and do not trigger this.

</b> on line 1 does not match <i>, which was opened on line 1 and is still open.

<b><i>bold italic</b></i>
Why:
Misnested tags. A browser repairs them silently and builds a different tree from the one written, while JSX requires every element to close in the reverse order it opened.
Fix:
Close the inner element first: <b><i>bold italic</i></b>.

</p> on line 2 has no matching opening tag: the <p> was already ended by the <div> on line 2.

<p>One
<div>Two</div></p>
Why:
A paragraph cannot contain a block element, so the parser ends the <p> as soon as the <div> starts. The </p> written after it then closes nothing, and a browser turns it into a second, empty paragraph.
Fix:
Use a <div> as the outer element, or keep only inline content such as <span>, <a> and <strong> inside the <p>.

Warning: Invalid DOM property `class`. Did you mean `className`?

Why:
HTML pasted into a component unchanged. JSX props are DOM property names, not HTML attribute names, so class, for, tabindex and the hyphenated SVG attributes are all spelled differently.
Fix:
Nothing to do here — every attribute is renamed from React’s own table, including the SVG ones such as stroke-width and xlink:href.

The `style` prop expects a mapping from style properties to values, not a string.

Why:
An inline style attribute copied as a string. React requires an object and throws on a string, so the component fails to render at all.
Fix:
Nothing to do here — the declarations become an object with camelCased keys, and a semicolon inside url() or quotes is not mistaken for the end of one.

Unexpected token. Did you mean `{'}'}` or `&rbrace;`?

Why:
A brace or > in text, such as a code sample, which JSX reads as the start of an expression or the end of a tag. The &rbrace; that Babel and TypeScript suggest is an HTML5 name neither of them decodes, so it renders literally.
Fix:
Nothing to do here — braces are written as {'{'} and {'}'}, and < and > as &lt; and &gt;, which every JSX compiler reads back correctly.

Frequently asked questions

Why does class become className?
JSX props are DOM property names, and the DOM property for the class attribute is className. React warns about class with "Invalid DOM property `class`. Did you mean `className`?", and the same table renames for to htmlFor, tabindex to tabIndex and the hyphenated SVG attributes to camelCase.
Why does the output use defaultValue instead of value?
In React, an input with value but no onChange is a controlled field locked to that value: typing does nothing and React warns. defaultValue sets the starting value and leaves the field editable, which is what the HTML did. Inputs whose value is not typed into — checkbox, radio, hidden and the buttons — keep value.
Why are numbers in the style object written as strings?
Because React adds px to a bare number. width: 10 in a style object means 10px, while width: 10 in CSS is invalid and ignored. Strings make the style do exactly what the HTML did; change them to numbers yourself where you mean pixels.
Does it convert SVG from Figma or Illustrator?
Yes. Attributes such as stroke-width and xlink:href are camelCased from React’s table, element names like linearGradient keep their case, the XML declaration is removed and CDATA is unwrapped. Editor metadata in namespaces such as sodipodi: cannot be written as JSX, so it is removed and listed.
Is my HTML uploaded anywhere?
No. The conversion runs entirely in your browser, and nothing you paste is sent to a server.

Last updated