Guide
Converting HTML into React JSX without breaking it
JSX looks like HTML, which is why pasting markup into a React component feels as if it should just work. It does not: JSX is JavaScript, so attribute names, inline styles, comments, closing tags and even curly braces in text follow different rules, and a single difference stops the build. This converter rewrites HTML into a JSX or TSX component and points out the parts that need a human decision.
Attribute names
Some HTML attributes clash with JavaScript keywords or follow DOM property names in React. class becomes className, for becomes htmlFor, and multi-word attributes take camelCase: tabindex → tabIndex, readonly → readOnly, maxlength → maxLength, srcset → srcSet, crossorigin → crossOrigin. The converter knows 39 of these renames. data- and aria- attributes keep their hyphens, as React expects. Other hyphenated attributes, which are mostly SVG presentation attributes, are camelCased: stroke-width becomes strokeWidth and fill-rule becomes fillRule.
Inline styles become objects
In JSX, style takes an object, not a string. style="max-width: 420px; margin: 0 auto; padding: 24px" becomes style={{ maxWidth: 420, margin: '0 auto', padding: 24 }}: property names are camelCased, pixel values become plain numbers (React adds px), and other values stay strings. Unitless properties such as line-height, z-index and opacity keep their numbers as they are, and custom properties such as --gap keep their names in quotes.
Tags, text and comments
Every element must be closed in JSX, so void elements such as <input>, <img> and <br> become self-closing. JSX also needs a single root, so the output is wrapped in a fragment <>…</> when the markup has several top-level elements or loose text. Curly braces in text would start JavaScript expressions, so they are escaped as {'{'} and {'}'}. HTML comments become JSX comments, {/* … */}.
Form fields and event handlers
In React, an input with a value becomes controlled and cannot be typed into without state. To keep the behaviour of the original HTML, value and checked on form fields are converted to defaultValue and defaultChecked. Inline handlers such as onclick="track('signup')" cannot run as strings, so they become onClick={() => { /* track('signup') */ }} with a warning, leaving you to move the code into a real function.
A worked example
The Sample is a sign-up card. The output starts with export default function SignupCard(): JSX.Element {, keeps the comment as {/* Sign-up card */}, and turns the wrapper into <div className="card" style={{ maxWidth: 420, margin: '0 auto', padding: 24 }}>. The label becomes <label htmlFor="email">Email</label>, the email input ends with autoFocus />, the SVG keeps strokeWidth="2" fillRule="evenodd", and the text “Prices in {currency}” becomes Prices in {'{'}currency{'}'} so React does not treat it as a variable. One warning explains the onclick stub.
What still needs attention
Inline <script> elements are kept as text with a warning, because React does not execute scripts inserted this way; load them with useEffect or your framework’s script helper. Repeated markup is a candidate for map() over data, and hard-coded text is a candidate for props. Very malformed HTML, such as unclosed tags, produces a warning listing the unclosed elements; fix those in the source first.
Related tools
- SVG to React Native converts icons for mobile apps.
- CSS to Tailwind turns the stylesheet into utility classes.
- JSON to TypeScript types the data the component will render.
- HTML Accessibility Checker reviews the markup before converting.
Checked against the current version of the HTML to JSX on .

