Skip to main content

HTML to JSX: The Renames React Wants and How to Skip the Busywork

A practical guide to converting HTML to JSX for React — class to className, for to htmlFor, self-closing void tags, and turning inline style strings into objects.

Published By Li Lei
#react #jsx #html #frontend #converter

HTML to JSX: The Renames React Wants and How to Skip the Busywork

You found a perfect chunk of markup — a pricing card from a component library, a hero section a designer exported, an email template marketing wants reused — and you paste it into a .jsx file. The compiler stops you cold on the very first line. class is not allowed. Then for. Then the inline style string. Then a stray <img> with no closing slash. JSX looks like HTML, but it is not HTML, and the gap between the two is a list of small, mechanical rules that React enforces with zero patience.

This post walks through what actually changes when HTML becomes JSX, why each rule exists, and how to do the whole conversion in one paste instead of forty find-and-replace passes.

JSX Is JavaScript Wearing an HTML Costume

The core fact behind every rule: JSX compiles down to JavaScript function calls. <div className="card"> becomes React.createElement('div', { className: 'card' }). Because it is JavaScript under the hood, JSX cannot use words that JavaScript has already claimed, and it cannot accept loose HTML conventions that a browser parser would happily forgive.

That single fact explains the three changes you hit most often, plus a handful of smaller ones. Once you see why they exist, they stop feeling arbitrary.

The Renames: class to className, for to htmlFor

class and for are reserved words in JavaScript — class declares a class, for starts a loop. Since JSX props are passed as a plain object, you cannot use those names as keys without confusing the parser. React's answer was to borrow the DOM IDL property names instead: the live DOM exposes element.className and label.htmlFor, so those are what JSX uses.

The same DOM-property logic drives a longer list of renames:

  • tabindex becomes tabIndex
  • readonly becomes readOnly
  • maxlength becomes maxLength
  • srcset becomes srcSet
  • every onclick / onchange handler becomes camelCased onClick / onChange

One trap worth pinning down: aria-* and data-* attributes are NOT camelCased. React keeps aria-label and data-id hyphenated on purpose. If you reflexively camelCase those along with the rest, you break them. Only the React-specific set gets renamed.

Void Tags Have to Close Themselves

In HTML you can write <img src="logo.png"> or <br> or <input type="text"> and the browser knows those elements never have children. JSX has no such forgiveness — every element must be explicitly closed. So void elements become self-closing: <img src="logo.png" />, <br />, <input type="text" />. Miss one slash and the parser swallows everything after it as a child of the unclosed tag, producing an error message that points nowhere near the actual problem.

Inline Styles Become Objects, Not Strings

This is the one that surprises people most. In HTML, style is a string: style="color:red;margin-top:8px". In JSX, the style prop takes a JavaScript object whose keys are camelCased CSS properties:

style={{ color: 'red', marginTop: '8px' }}

React applies styles through the DOM element.style API, which is keyed by camelCased property names — marginTop, not margin-top; backgroundColor, not background-color. So the conversion has to split the CSS string on semicolons, camelCase each property name, and emit a real object literal. Custom properties like --brand are the exception: they pass through verbatim, because React forwards CSS variables untouched.

Boolean attributes get a tweak too. HTML uses presence-or-absence (<input disabled>); JSX wants a value, so the clean form is disabled={true}. The bare <input disabled /> shorthand also works if you prefer it.

A Worked Example

Here is a realistic snippet — a label, an input, and a styled note — exactly the kind of thing a CMS or a Bootstrap doc page hands you.

Input HTML:

<!-- signup field -->
<div class="form-row" style="margin-top:12px;background-color:#f7f7f7">
  <label for="email">Email address</label>
  <input type="email" id="email" class="input" maxlength="120" disabled>
  <img src="/lock.svg">
</div>

Output JSX:

{/* signup field */}
<div className="form-row" style={{ marginTop: '12px', backgroundColor: '#f7f7f7' }}>
  <label htmlFor="email">Email address</label>
  <input type="email" id="email" className="input" maxLength={120} disabled={true} />
  <img src="/lock.svg" />
</div>

Count the changes in those four lines: the comment switched to {/* */}, class to className (twice), for to htmlFor, maxlength to maxLength, the inline style string to a camelCased object, the bare disabled to disabled={true}, and both the <input> and <img> picked up closing slashes. That is eight separate edits across a tiny fragment. Multiply it across a 300-line landing page and you understand why doing it by hand is a chore worth skipping.

Why I Stopped Doing This by Hand

I used to convert markup the brute-force way: paste it, hit save, read the first compiler error, fix it, save again, read the next one. On a real landing-page migration that loop ran for the better part of twenty minutes, and the errors arrive one at a time — the compiler tells you about the first class but says nothing about the forty after it, or the six inline styles, or the unclosed <img> halfway down. The day I started running the whole file through a converter first, that twenty minutes dropped to a single paste-and-copy. The markup landed already compiling, and I spent my actual attention on the part that matters: swapping hardcoded text for props and splitting the blob into real components.

Do the Whole Conversion in One Paste

The HTML to JSX converter applies every rule above in a single pass: the renames, the self-closing tags, the inline style objects, the comment syntax, the boolean attributes — and it drops the <!DOCTYPE> if you paste a full page. Parsing runs through the browser's native DOMParser in read-only mode, so nothing you paste executes and nothing gets uploaded. You can optionally wrap the result in a function component with the indentation you choose, ready to drop straight into a file.

A couple of honest caveats. It converts; it does not sanitize. An onclick in your source becomes onClick in the output, and a <script> stays a <script> — review the JSX before shipping, especially if the HTML came from somewhere untrusted. And if you paste an entire document, grab the <body> contents rather than trying to render a bare <html> as a component.

Once your JSX compiles, the CSS that came along for the ride often needs the same treatment — messy inline styles and minified stylesheets read better after a pass through the CSS formatter before you lift the rules into a stylesheet or a styled-component.

The rules separating HTML from JSX are small, but there are a lot of them, and the compiler reveals them one painful error at a time. Convert first, then build.


Made by Toolora · Updated 2026-06-13