DEV Community

Cover image for HTML to JSX Converter: The Complete Guide to Converting HTML to React (and Back)
Roman Popovych
Roman Popovych

Posted on

HTML to JSX Converter: The Complete Guide to Converting HTML to React (and Back)

You have a working website. Hundreds of pages, blog posts, product cards — all in plain HTML. Then you decide to migrate to Next.js. And suddenly every class needs to become className, every for needs to become htmlFor, every style="color: red" needs to become style={{ color: 'red' }}. Multiply that by two hundred articles.

That's exactly the situation I was in. I had an old content site built with static HTML, and I was porting it to Next.js — component by component, page by page. The HTML structure was fine. The logic was fine. But the JSX syntax differences were burning hours I didn't have. Copy, paste, get a red error, fix class, fix for, fix the inline styles, repeat.

That's why I built the HTML to JSX Converter — and the reverse JSX to HTML Converter. This guide explains what they do, when you need them, and exactly what transformations happen under the hood.


Why JSX Is Not HTML (Even Though It Looks Like It)

JSX is a JavaScript syntax extension. It looks like HTML, it renders like HTML, but it compiles to React.createElement() calls — which means it lives inside JavaScript and must follow JavaScript rules.

HTML was designed to be forgiving and markup-centric. JSX was designed to sit comfortably inside .js and .tsx files without conflicts. That's why the attribute names are different.

The difference HTML JSX Why it changed
CSS class class="card" className="card" class is a reserved word in JavaScript
Label binding for="email" htmlFor="email" for is a reserved word in JavaScript
Event handlers onclick="fn()" onClick={fn} camelCase convention + function reference, not string
Multi-word events onmouseenter onMouseEnter camelCase required for all on* attributes
Inline styles style="color: red" style={{ color: 'red' }} Must be a JavaScript object, not a CSS string
CSS property names font-size: 14px fontSize: '14px' kebab-case → camelCase to match DOM style properties
Void elements <br>, <input> <br />, <input /> JSX requires all elements to be explicitly closed
HTML comments <!-- text --> {/* text */} HTML comment syntax is not valid inside JSX
Boolean attributes readonly, disabled readOnly, disabled Some are renamed; standalone booleans work in both
Numeric attributes tabindex="1" tabIndex={1} Numbers are passed as JS expressions, not strings

These are systematic differences — they apply everywhere, every time. Which is exactly what a converter is for.


When Do You Actually Need an HTML to JSX Converter?

Most developers encounter this problem in one of these scenarios:

Situation What you have What you need
Migrating a static site to React / Next.js Hundreds of HTML templates and articles JSX components ready to paste into your codebase
Using AI-generated code HTML output from ChatGPT, Claude, Copilot Valid JSX you can drop into a component
Converting design exports Figma-to-HTML, Webflow export, email template React-compatible JSX without manual attribute hunting
Copying UI library examples HTML-first component docs (Bootstrap, Tailwind CDN examples) JSX to use inside your React project
Building email template previews React component for email rendering Plain HTML for email client compatibility
Writing documentation React component markup HTML snippet for docs, README, Storybook notes
Sharing with non-React devs JSX component structure Standard HTML a backend dev or designer can read
CMS integration Headless CMS preview HTML JSX for your Next.js front end

The migration case is the biggest one. When you move from a WordPress blog, a Jekyll site, a Webflow export, or any legacy HTML-based setup to a React or Next.js project, you face exactly the same problem at scale. The content is correct. The HTML structure is correct. But JSX won't compile until every attribute is renamed.

Doing this by hand on ten components is annoying. Doing it on two hundred pages is a week of pure mechanical work.


How the HTML to JSX Converter Works

The HTML to JSX converter applies a series of deterministic transformation rules to your input. It runs entirely in your browser — no file is uploaded, no server is involved, nothing leaves your tab.

Here's what it does, in order:

1. HTML comments → JSX expression comments

<!-- This is a comment -->{/* This is a comment */}
Enter fullscreen mode Exit fullscreen mode

2. Attribute renaming

All known HTML→JSX attribute pairs are renamed:

  • classclassName
  • forhtmlFor
  • tabindextabIndex
  • readonlyreadOnly
  • maxlengthmaxLength
  • minlengthminLength
  • contenteditablecontentEditable
  • autocompleteautoComplete
  • autofocusautoFocus
  • crossorigincrossOrigin
  • cellpaddingcellPadding
  • rowspanrowSpan
  • colspancolSpan
  • and more

3. Event handler camelCasing

All on* attributes are converted to camelCase, including the non-obvious multi-word ones:

HTML JSX
onclick onClick
onchange onChange
onmouseenter onMouseEnter
onmouseleave onMouseLeave
onkeydown onKeyDown
onkeyup onKeyUp
ondblclick onDoubleClick
ontouchstart onTouchStart
ontouchend onTouchEnd
onpointerdown onPointerDown
ondragstart onDragStart
onanimationstart onAnimationStart
ontransitionend onTransitionEnd

4. Inline style string → JavaScript object

style="color: red; font-size: 14px; margin-top: 8px"

style={{ color: 'red', fontSize: '14px', marginTop: '8px' }}
Enter fullscreen mode Exit fullscreen mode

Every CSS property is camelCased (font-sizefontSize), and the whole thing becomes a JS object expression.

5. Void element self-closing

<br><br />
<img src="photo.jpg" alt="photo"><img src="photo.jpg" alt="photo" />
<input type="text"><input type="text" />
Enter fullscreen mode Exit fullscreen mode

Full Example: HTML → JSX

<!-- Input HTML -->
<div class="card" onclick="handleClick()">
  <label for="email">Email address</label>
  <input type="email" id="email" readonly tabindex="1"
         style="border: 1px solid #ccc; font-size: 14px; border-radius: 4px">
  <br>
  <img src="avatar.png" alt="User avatar">
  <p style="color: red; font-weight: bold;">Required field</p>
</div>
Enter fullscreen mode Exit fullscreen mode
// Output JSX
<div className="card" onClick={handleClick}>
  <label htmlFor="email">Email address</label>
  <input type="email" id="email" readOnly tabIndex={1}
         style={{ border: '1px solid #ccc', fontSize: '14px', borderRadius: '4px' }} />
  <br />
  <img src="avatar.png" alt="User avatar" />
  <p style={{ color: 'red', fontWeight: 'bold' }}>Required field</p>
</div>
Enter fullscreen mode Exit fullscreen mode

Zero manual edits needed. Paste, convert, use.


How the JSX to HTML Converter Works

The JSX to HTML converter runs all the same rules in reverse.

What it converts:

JSX HTML
className="card" class="card"
htmlFor="email" for="email"
onClick={fn} onclick="fn"
tabIndex={1} tabindex="1"
readOnly readonly
style={{ color: 'red', fontSize: '14px' }} style="color: red; font-size: 14px"
<br /> <br>
<img src="…" /> <img src="…">
<div /> <div></div>
{/* comment */} <!-- comment -->
<>…</> unwrapped children
<React.Fragment>…</React.Fragment> unwrapped children

JSX expression values are also resolved:

JSX attribute HTML output
disabled={true} disabled (bare boolean attribute)
hidden={false} removed entirely
data-value={null} removed entirely
tabIndex={1} tabindex="1" (value stringified)
aria-label={"Close"} aria-label="Close"

Style object → CSS string:

style={{ backgroundColor: '#fff', fontSize: '16px', zIndex: 100, WebkitTransform: 'translateX(10px)' }}

style="background-color: #fff; font-size: 16px; z-index: 100; -webkit-transform: translateX(10px)"
Enter fullscreen mode Exit fullscreen mode

PascalCase vendor prefixes (WebkitTransform) are correctly converted to hyphenated CSS (-webkit-transform). Unitless numbers (zIndex: 100) are preserved as-is.


What the Converter Does NOT Handle (And Why That's Fine)

JSX is a superset of HTML. Some JSX constructs have no HTML equivalent and the converter intentionally leaves them intact so you can see what needs manual work:

JSX construct Why it can't auto-convert What to do
{variable} JavaScript value resolved at runtime Replace with the actual rendered value
{condition && <Element />} Runtime conditional Manually output the element or nothing
<Button variant="primary" /> React component, not an HTML tag Replace with the HTML the component renders
key={id} React-internal prop, no DOM output Drop it — HTML has no key concept
ref={myRef} React-internal prop Drop it
dangerouslySetInnerHTML React-specific API Extract the __html value manually

This is by design. The converter handles the mechanical part — attribute naming, style syntax, self-closing tags, fragment unwrapping. The dynamic and component-level logic you keep.


The Migration Workflow: Static Site to Next.js

Here's the actual workflow if you're in the same situation I was — porting a content site to Next.js:

  1. Start with the page layout. Convert your <header>, <nav>, <footer> HTML to JSX. The converter handles the structural attributes instantly.

  2. Handle content blocks. For article bodies with mixed HTML (headings, paragraphs, lists, code blocks), paste each section and convert. If the content is CMS-driven, you'll likely render it with dangerouslySetInnerHTML anyway — in which case the original HTML is what you want.

  3. Convert form elements. Forms are where HTML→JSX differences hurt the most: for, readonly, maxlength, autocomplete, novalidate — they all need renaming. The converter gets all of them.

  4. Handle inline styles. If your legacy HTML uses inline styles for layout or one-off overrides, the converter translates all of them to JSX style objects. Then you can decide whether to keep them or extract to CSS modules.

  5. Wire up events. The converter renames onclick to onClick, onsubmit to onSubmit, etc. You still need to point the handler at an actual function in your component — but the attribute is in the right format.

  6. Use JSX to HTML for the reverse. If any new React components need to be shared with a non-React context (email templates, legacy CMS pages, documentation), run them through the JSX→HTML converter to get clean HTML output.


Frequently Asked Questions

Does the converter send my code anywhere?
No. Both converters run entirely in your browser using JavaScript string processing. No server receives your code. No file is uploaded. You can verify this in browser DevTools → Network tab — no requests will appear while converting.

Does it handle SVG attributes?
Yes. SVG inline attributes in JSX follow the same camelCase convention as HTML. stroke-widthstrokeWidth, fill-opacityfillOpacity, viewBox stays as-is.

What about data-* and aria-* attributes?
Both are kept unchanged. data-id, data-value, aria-label, aria-expanded are identical in HTML and JSX — no transformation needed.

Does it add the React import statement?
No — in React 17+ with the new JSX transform, you don't need import React from 'react' anymore. If you're on an older setup, add it manually.

Can I convert a full HTML page?
Technically yes, but <html>, <head>, and <body> tags are not a valid React component structure. Extract the content section (<main>, <article>, your component markup) and convert that.

Does it work with Tailwind CSS classes?
Yes. class="flex items-center gap-4 text-sm font-medium" becomes className="flex items-center gap-4 text-sm font-medium" — all class strings are passed through unchanged, only the attribute name changes.


Try It Now — Free, No Account, No Upload

Both tools are free, run entirely in your browser, and require no account or signup:

If you're in the middle of a React or Next.js migration, these will save you a significant amount of purely mechanical work. Paste your markup, convert, and move on to the parts that actually need thinking.


Found a conversion edge case that the tool doesn't handle? Drop it in the comments — the tricky cases are usually the most instructive ones.

Top comments (0)