99tools

HTML / SVG → JSX

runs in your browser

Paste HTML or SVG and get JSX: className, htmlFor and camelCased SVG attributes, style as an object, braces and comments escaped, void tags closed.

Output

Attribute names take React’s spelling, style becomes an object, comments and braces are escaped, void tags close themselves. Anything that needed a judgement call is listed under the output.

HTML or SVG

JSX

about this tool

What changes

JSX is HTML with JavaScript's rules, and the differences are exactly the things this converter does:

  • Attribute names take React's spelling: classclassName, forhtmlFor, tabindextabIndex, readonlyreadOnly, colspancolSpan, and the rest of React's list. SVG presentation attributes are camel-cased (stroke-widthstrokeWidth, fill-rulefillRule); namespaced ones too (xlink:hrefxlinkHref, xml:spacexmlSpace). Attributes that are camel-cased in SVG itself (viewBox, preserveAspectRatio, gradientUnits) are kept, even when pasted in lowercase. data-* and aria-* are left alone.
  • style becomes an object: margin-top: 4pxmarginTop: '4px', -webkit-line-clampWebkitLineClamp, --accent stays a quoted key. Values are kept as the strings they were.
  • Comments become {/* … */}; braces in text become {'{'} and {'}'}, since JSX would read them as expressions.
  • Void elements (br, img, input, meta…) self-close.
  • Form fields stay uncontrolled: valuedefaultValue, checkeddefaultChecked, and a <textarea>'s content becomes its defaultValue. A note says so, because a controlled field is a deliberate choice.
  • Inline event handlers become arrow functions — onclick="save()"onClick={() => { save() }} — with a note asking you to check that the body still means something inside a component.
  • Small integer values of tabIndex, colSpan, rowSpan, maxLength, width and height are written as numbers.

Raw elements

The contents of <pre> and <style> are given as one string expression, {"…"}, so JSX cannot collapse their whitespace or read their braces. <script> is dropped with a note: code belongs in the component or a module, not in markup.

Whitespace and layout

JSX trims whitespace around line breaks, so a space between two elements on separate lines disappears. To keep prose intact, an element whose children are text mixed with simple inline elements is written on one line; elements whose children are elements are written one per line, indented. Runs of whitespace inside text collapse to one space, as the browser would.

Wrapping

Several top-level nodes are wrapped in a fragment (<>…</>) so the result is one expression, and a note says so. "Component" wraps the JSX in export default function Name() { return (…); }, and when the single root is an <svg> it spreads {...props} onto it, which is how an icon component usually wants to be used. A name that is not a valid capitalised identifier is replaced with Component and noted.

Limits

The output is JSX for the web, not React Native SVG, and it is untyped: add your own Props. The parser keeps every byte and every element name's case (the browser's own parser would drop a stray <tr> and lowercase linearGradient), and when it has to close an element for you it says which. Entities such as &amp; are kept as written, since JSX reads them. Nothing here decides that a value should have been an expression; {} in an attribute value is a string.

If you only want the markup indented rather than converted, HTML formatter & minifier does that. For an SVG, running it through SVG optimizer first removes the editor cruft that would otherwise become JSX attributes.

questions

Why did value become defaultValue and checked become defaultChecked?
In React, value and checked make a field controlled: React sets it on every render, so without an onChange handler the field cannot be typed into. Static HTML means an initial value, and defaultValue and defaultChecked are how JSX says that. If you want a controlled field, change them back and add onChange.
What happens to onclick="doThing()"?
It becomes onClick={() => { doThing() }}, and a note asks you to check it. The body ran against the global page in HTML; inside a component doThing may not be in scope, and this may be the moment to make it a real handler.
Why is the text inside <pre> and <style> wrapped in braces and quotes?
JSX collapses whitespace and reads { and } as expressions. Giving the content as one string expression keeps every space and newline and every brace exactly as written, which is what pre and style need.
Does it produce React Native SVG?
No. The output is JSX for the web: <path>, <circle> and friends as the browser renders them. React Native needs the react-native-svg component names and import lines, which is a different target; SVGR does that conversion.
Why not just parse it with the browser?
The browser’s HTML parser drops or moves what it considers misplaced — a stray <tr> vanishes, and attribute case is lost. This converter keeps every byte of the input, keeps SVG element names such as linearGradient in their case, and tells you about a tag it had to close for you rather than silently fixing the tree.