How to Convert HTML to JSX: The Complete Developer's Guide
Convert HTML markup to valid React JSX — the attribute renames, the inline-style object transform, the self-closing void elements, and the few cases where the converter hands the wiring back to you.
- Convert HTML to React JSX.
- Why HTML Isn't JSX.
- Covers attribute renames (the big list).
- Covers event handlers: camelcase and functions.
- Covers self-closing void elements.
Why HTML Isn't JSX
If you've ever pasted a snippet of HTML into a React component expecting it to just work, you've seen the problem. The browser console lights up with warnings — Invalid DOM property 'class', Unknown event handler property 'onclick', Inline style should be a JavaScript object — and half your markup silently stops behaving the way it did on a plain HTML page.
JSX *looks* like HTML, which is the whole point. React's designers wanted markup to feel familiar. But JSX is JavaScript, not HTML, and JavaScript comes with its own rules: class is a reserved keyword, event handlers are functions not strings, and CSS inline styles are objects because you need to compose them at runtime.
The HTML to JSX Converter runs through the mechanical fixups in one paste: attribute renames (class → className, for → htmlFor, 30+ others), event handlers to camelCase, void elements self-closed, inline styles parsed into JavaScript objects, and HTML comments converted to the {/* */} JSX form. Optionally it wraps the whole thing in a default-exported functional component with a name you choose.
This guide walks through what the converter handles, the specific rename rules, the style-object conversion, and the few edge cases — SVG namespaces, boolean attributes, inline scripts — where you'll still want to look at the output before committing.
Attribute Renames (The Big List)
The single largest category of HTML-to-JSX changes is attributes that got renamed because their HTML names conflict with JavaScript reserved words or don't match JavaScript's camelCase convention. Here's the full list the converter knows about:
The two most common — you'll see these on every paste:
class is a reserved word in JavaScript (introduced with ES6's class syntax). for is the for loop keyword. React uses className and htmlFor to sidestep both.
Attributes that became camelCase:
Hyphenated attributes that become camelCase:
data-* and aria-*.** These stay hyphenated in JSX — React treats them specially. data-testid, aria-label, aria-describedby all work as-is without renames. The converter leaves them alone.The conversion is case-insensitive, so ONCLICK, OnClick, and onclick all become onClick. The pattern uses word boundaries, so a string like class="noclass" inside a value (not an attribute name) won't get touched.
Event Handlers: camelCase and Functions
HTML event handlers are strings. JSX event handlers are functions. The converter handles the naming part; you have to handle the value part.
background-size animation or @property registered custom properties instead.What the converter does. Every on<something>= attribute gets capitalized:
The pattern is simple: on, then the event name with its first letter capitalized. The regex handles any on* attribute, so less common events (onpointerdown, onwheel, ontransitionend) all get the same treatment.
What the converter does NOT do. The value stays a string. So this:
Becomes this:
Which *looks* right but isn't — React expects a function reference, not a string. You'll get a console warning (Expected onClick listener to be a function, instead got a value of 'string' type), and the handler won't fire. To fix it:
Or inline:
This is deliberate — the converter can't invent a function that doesn't exist, and silently generating something like onClick={() => eval("submit()")} would be worse than leaving the wiring for you.
Self-Closing Void Elements
Void elements are HTML tags that can't have children — <img>, <input>, , <hr>, <meta>, <link>, <area>, <base>, <col>, <embed>, <param>, <source>, <track>, <wbr>. In HTML you can write <img src="x"> without a closing tag and the parser knows what to do. In JSX, you can't — all tags must either have a matching close tag or be self-closed with />.
The converter handles this automatically. Every void element gets the /> suffix added:
The regex is clever about not double-closing things that are already self-closed — so <img src="x" /> stays as <img src="x" />, it doesn't become <img src="x" / />. The lookbehind (?<!\/) in the pattern skips anything already ending in /.
React.createElement(tag, props, ...children) calls. Without a close tag or self-close marker, the parser has no way to know where the element ends. <img src="x">next could mean "img followed by span" or "img with span as a child." Self-closing removes the ambiguity.Inline Styles: String to Object
This is the most transformative conversion the tool does, and the one that saves the most typing. In HTML:
In JSX:
The converter splits the style string on semicolons, parses each property: value pair, camelCases the property name (kebab-case → camelCase), and wraps everything in a double-braced JSX expression — the outer {} is the JSX expression delimiter, the inner {} is the JS object literal.
Rules the converter follows:
- Property names get camelCased:
background-color→backgroundColor,border-top-width→borderTopWidth,-webkit-transform→WebkitTransform - Values stay as strings (always quoted):
"red","16px","0 auto" - CSS custom properties (variables) starting with
--are preserved as-is and wrapped in quoted keys:{"--brand-color": "#FF6B6B"} - Invalid property names (ones that need quoting to be valid JS identifiers) get quoted automatically
- Empty values and malformed pairs are silently dropped
A subtle point: values stay strings. React accepts both strings and numbers for most properties, but the converter always emits strings. For properties that take unitless numbers (like zIndex, opacity, flexGrow), you can manually remove the quotes after converting — but it's not required for correctness.
What doesn't convert. The converter only handles inline styles. Stylesheets in <style> blocks or external CSS files stay as they are — which is usually what you want, since those don't have the JSX-object issue in the first place.
HTML Comments to JSX Comments
HTML comments look like <!-- this is a comment -->. In JSX, <!-- isn't a recognized syntax — the parser treats < as an element opener and fails. You either have to delete the comments or convert them to the JSX form.
The converter takes the second path:
The wrapping {} is the JSX expression delimiter; /* */ is standard JS block comment syntax. The result is a valid JSX expression that evaluates to undefined at runtime, which React renders as nothing.
Multi-line comments work the same way:
*/, the JS parser will end the comment early and probably throw a syntax error. This is rare in practice but worth knowing if you're converting machine-generated HTML that might have nested comments.Boolean Attributes
In HTML, certain attributes are "boolean" — just their presence means true. <input disabled> means the input is disabled; there's no value after the attribute. You can also write <input disabled="disabled"> or <input disabled=""> — all three are equivalent.
In JSX, the bare-attribute form works for some attributes but is fragile. The converter makes the assignment explicit:
The attributes the converter treats as boolean: checked, disabled, required, readOnly, hidden, selected, multiple, default, reversed, open, autoFocus, autoPlay, controls, loop, muted, playsInline.
Why ={true} instead of just the attribute name? Because in real React code, these values usually come from state or props, not hardcoded. Explicit ={something} is the natural shape to edit:
So the converter generates the pattern you'll actually type in half a second, rather than the terser form you'd have to expand anyway.
Wrapping the Output as a Component
The converter has a "Wrap as component" toggle. When enabled, the output is the raw JSX wrapped inside a complete functional component with a default export:
The <>...</> is a React Fragment — it lets the component return multiple top-level elements without wrapping them in an extra <div>. You can rename the component using the text input next to the toggle (only alphanumeric characters are accepted, so My-Component gets sanitized to MyComponent).
When to use Wrap mode:
- Converting a whole HTML page body into a new component file — saves the boilerplate
- Building a design-to-code workflow where you paste a Figma-export and want a ready file
- Quick prototyping — drop the output into any React project and import it
When to leave it off:
- Converting a snippet you'll paste *inside* an existing component's JSX — you just want the fragment
- Converting content that will go into an existing component's
returnstatement - Building a template string or other embedding case where you don't want the
exportline
Edge Cases and Gotchas
The places where the converter's output needs a second look before it's production-ready.
Inline <script> tags. Scripts inside HTML stay as literal text in the JSX output. React will render them as <script> elements, but they won't execute — React strips inline scripts on render for security reasons. If your pasted HTML has an important inline script, the logic needs to move into a component effect or be loaded via a src= attribute.
SVG with xmlns:xlink. The xmlns:xlink attribute gets partially handled (namespace attributes are a specific category). Modern SVGs mostly don't use xlink:href anymore — they use plain href — so this is rarely a problem. If you're converting an older SVG icon library, spot-check the output.
Style property with CSS custom properties. A declaration like style="--brand: #FF6B6B; color: var(--brand);" converts correctly (the --brand key is preserved, the var() reference stays as a string value). But if you're passing variables from component props, you'll need to switch to template literals or the spread syntax manually.
Multiple root elements in a snippet. If your pasted HTML has multiple top-level elements (like two <div> siblings), the converter produces valid JSX for each one — but the wrapped-component mode uses a <> fragment so both get returned. Without wrap mode, you'll need to wrap the output yourself in something (a fragment, a <div>, or array syntax) before React accepts it.
Character entities in text nodes. HTML entities like &, <, are preserved as-is in the output. JSX understands these in text content, so they render correctly. But inside attribute values, they get tricky — & inside a href works fine, but inside a JS expression it won't. Spot-check hrefs and similar.
Common Workflows
Three realistic ways developers use this tool.
1. Figma / design-tool export to component. Many design tools now export HTML/CSS snippets for handoff. These are almost always HTML (not JSX), even in 2026. Paste the export, enable "Wrap as component", name it after the design frame, copy, and drop into your components/ directory. Clean up the inline styles by extracting to a stylesheet or Tailwind classes afterward.
2. Moving a legacy HTML page into React. Converting an existing static site to React one page at a time? Paste the <body> contents section by section. The converter handles the attribute renames and style syntax; you'll still need to split the markup into logical components, replace string event handlers with real functions, and wire up any forms to controlled inputs. But the boring 60% of the conversion — renaming 200 class attributes — is done in a paste.
3. Email templates and marketing HTML. Email HTML is a nightmare — inline styles everywhere, tables for layout, vendor-specific <!--[if mso]> conditional comments. The converter handles the inline-style conversion cleanly. The conditional comments become JSX comments (which is fine — they just won't be rendered). The tables-for-layout stay as tables, which is still correct for email-like contexts.
The pattern. Paste → pick toggle (wrap or not) → copy → drop into your file → fix the things the converter explicitly leaves for you (event handler functions, dynamic values, script logic). What you save is the mechanical part, which is exactly the part that's soul-crushing to do manually at any scale.
HTML vs JSX: Quick Reference
A cheat sheet you can bookmark. Every difference between HTML and JSX, with the converter's output in the middle.
The lines marked "needs fn ref" are the places the converter can't help you — where the string value of an attribute needs to become a real JavaScript function. Everything else is mechanical.
What to Do After Converting
The converter handles the syntax. Code quality is your job. Here's the checklist I run through before committing JSX converted from HTML.
Replace string event handlers with functions. Search the output for patterns like onClick=" — any quote-wrapped value on an on* attribute needs to become a function reference.
Move inline styles to stylesheets. Inline styles in JSX are valid, but they make components harder to theme, harder to reuse, and harder to optimize. For one-off values (like a dynamic width from a prop), keep them inline. For anything reused, move to CSS/SCSS/Tailwind classes. If you're on Tailwind, the CSS to Tailwind converter handles the next step.
Check form inputs. HTML forms using <input> with native attributes (required, pattern, native validation) work in React but feel weird — most React codebases use controlled inputs with explicit state. Decide whether to keep uncontrolled (simpler, native) or switch to controlled (more React-idiomatic, more flexible).
Extract reusable pieces. A converted HTML page is one giant component. Look for repeated patterns — cards, list items, form rows — and break them into their own components that take props. This is the point where you get the React-architecture benefits.
Accessibility pass. The converter preserves your original HTML's accessibility — which is good if the original was accessible and a problem if it wasn't. Check <img> tags have alt text. Check form inputs have labels. Check focus states work. React's tooling (like eslint-plugin-jsx-a11y) can catch most issues automatically once the JSX is in a real project.
Frequently Asked Questions
Yes, but with caveats. The converter handles the syntax conversion for any amount of HTML. What doesn't translate cleanly: inline script tags (they won't execute in React), DOCTYPE declarations (not valid JSX), html, head, and body tags (these are owned by the page shell in a React app, not a component). For a full-page conversion, paste the body contents only — that gives you the component-shaped output.
The output is plain JSX, which is valid in both .jsx and .tsx files. If you're using TypeScript, the component will work immediately but won't have types on its props (since the converter isn't inferring a props interface from nothing). When you enable Wrap as component, the exported component takes no props — add a typed props parameter after you paste the output into your .tsx file.
Mostly yes. Simple SVG icons — the kind you'd copy from an icon library — convert cleanly: class becomes className, attributes like stroke-width become strokeWidth, and the output is valid JSX you can drop into a component. Edge cases: namespaced attributes like xmlns:xlink and xlink:href may need manual adjustment for very complex SVGs, and script tags inside SVG (used by some interactive SVGs) won't execute in React.
Because that's the form you'll actually edit. In real React code, boolean attributes almost always take a dynamic value — disabled={isLoading}, required={fieldRequired}, hidden={!shouldShow}. Generating the explicit ={true} form gives you the exact shape to swap in a state variable or prop. The terser bare-attribute form is legal JSX but fragile and less common in modern codebases.
The converter handles both single and double-quoted attribute values. If your HTML has something like
The entire tool runs in your browser — once the page has loaded, it works without a network connection. No paste of yours ever leaves your device. This is intentional because HTML conversions often include proprietary markup, design system code, and marketing copy that your employer doesn't want on someone else's server. For enterprise use, you can save the page locally (browser File > Save As) and use the saved copy as a local offline tool.
Use the HTML to JSX Converter — free, no signup required.
⚛ Open HTML to JSX Converter