We are experiencing higher levels of website traffic, we are trying to expand. Thank you for your understanding.
HTML

Converting HTML to JSX: The Complete Guide for React Developers

Every transformation needed to convert HTML to valid JSX: className, htmlFor, style objects, self-closing tags, SVG attributes, and the gotchas in between.

Start reading Use tools
Converting HTML to JSX: The Complete Guide for React Developers cover art

Every React developer hits this task eventually: you have a chunk of HTML, from a designer's handoff, a marketing template, an old static site, or copied straight out of DevTools, and you need it inside a React component. Paste it in unchanged and the compiler complains, or worse, it compiles and silently misbehaves. JSX looks like HTML but it is not HTML; it is syntax for JavaScript function calls, and that difference produces a specific, learnable set of transformations.

This guide covers every one of them, including the cases most cheat sheets skip.

Why JSX is not HTML

JSX compiles to React.createElement calls (or the modern jsx runtime equivalent). The attribute names you write become JavaScript object keys, which is why reserved words like class and for cannot be used, and why attributes follow DOM property naming (camelCase) instead of HTML attribute naming (lowercase). Once you internalize "I am writing JavaScript objects, not markup," every rule below makes sense.

The core transformations

1. class becomes className, for becomes htmlFor

<!-- HTML -->
<label for="email" class="form-label">Email</label>

// JSX
<label htmlFor="email" className="form-label">Email</label>

These are the famous two because class and for are JavaScript reserved words. React maps them to the DOM properties className and htmlFor.

2. Inline styles become objects

This is the transformation people get wrong most often, because it has three sub-rules: the value is an object not a string, property names are camelCased, and values are strings (or numbers, which React treats as pixels for most properties).

<!-- HTML -->
<div style="background-color: #1a1a2e; margin-top: 16px; z-index: 10">

// JSX
<div style={{ backgroundColor: '#1a1a2e', marginTop: 16, zIndex: 10 }}>

Note the double braces: the outer pair says "JavaScript expression here," the inner pair is an object literal. Also note zIndex: 10 stays unitless because z-index is a unitless property; React knows the difference and will not append px to it.

3. Self-close every void element

HTML lets you write <br>, <img src="...">, <input type="text">. JSX requires XML-style self-closing:

<img src="/logo.png" alt="Logo" />
<input type="text" name="q" />
<br />
<hr />

This applies to all void elements: area, base, br, col, embed, hr, img, input, link, meta, source, track, wbr.

4. camelCase nearly every attribute

Multi-word HTML attributes become camelCase DOM property names:

  • tabindex becomes tabIndex
  • readonly becomes readOnly
  • maxlength becomes maxLength
  • autocomplete becomes autoComplete
  • spellcheck becomes spellCheck
  • contenteditable becomes contentEditable
  • crossorigin becomes crossOrigin

The two exceptions that stay lowercase with hyphens: data-* and aria-* attributes pass through unchanged. aria-label stays aria-label, never ariaLabel.

5. Event handlers change shape entirely

<!-- HTML -->
<button onclick="submitForm()">Send</button>

// JSX
<button onClick={submitForm}>Send</button>

Three changes at once: camelCase name (onClick), braces instead of quotes, and you pass a function reference rather than a string of code to evaluate. If you are converting legacy markup with inline handlers, this is where the real porting work begins, because those handler strings reference global functions that need to become component logic.

6. Comments

<!-- HTML comment -->
{/* JSX comment */}

HTML comments are invalid inside JSX. Convert them to JavaScript comments wrapped in braces, or delete them.

The cases the cheat sheets skip

SVG attributes

Pasted SVG is full of hyphenated attributes, and almost all of them camelCase in JSX: stroke-width becomes strokeWidth, fill-rule becomes fillRule, clip-path becomes clipPath, stop-color becomes stopColor. Namespaced attributes convert too: xlink:href becomes xlinkHref (though plain href works in modern browsers and is preferred). A single exported icon can need a dozen of these changes, which is exactly the kind of mechanical work you should not do by hand.

Boolean attributes

HTML boolean attributes like disabled, checked, and required work bare in JSX (<input disabled />), but two have special handling: a controlled checked or value without an onChange handler triggers a React warning. For markup you are porting as static content, use defaultChecked and defaultValue instead.

Whitespace behaves differently

JSX trims leading and trailing whitespace on lines and collapses blank lines. Text that relied on a newline between inline elements rendering as a space can lose that space. If a converted nav suddenly has its links jammed together, add explicit {' '} spacers.

Unescaped entities and special characters

Literal < and { characters in text content will break the parse or be interpreted as expressions. Escape them as &lt; and {'{'}, or wrap the whole string in an expression: {'a < b and {braces}'}. HTML entities like &nbsp; and &copy; work fine in JSX text.

dangerouslySetInnerHTML for embedded HTML

Sometimes the right move is not converting at all. CMS content, rich text output, and third-party embeds belong in dangerouslySetInnerHTML:

<div dangerouslySetInnerHTML={{ __html: cmsContent }} />

The name is a deliberate warning: only do this with HTML you trust or have sanitized, because it is a direct XSS vector otherwise.

A worked example

<!-- Before: HTML -->
<div class="card" style="padding: 24px; border-radius: 8px">
  <img src="avatar.jpg" class="avatar">
  <!-- user info -->
  <label for="bio">Bio</label>
  <textarea id="bio" maxlength="200" spellcheck="false"></textarea>
</div>

// After: JSX
<div className="card" style={{ padding: 24, borderRadius: 8 }}>
  <img src="avatar.jpg" className="avatar" alt="" />
  {/* user info */}
  <label htmlFor="bio">Bio</label>
  <textarea id="bio" maxLength={200} spellCheck={false} defaultValue="" />
</div>

Count the changes in even this small block: two className, one style object, one self-closed image, one comment conversion, one htmlFor, two camelCased attributes, and a textarea converted to use defaultValue. Now imagine a 300-line template.

Converting at scale: a porting strategy

For a one-off snippet, convert and move on. For a whole template or static site, a little process pays off:

  • Convert markup first, extract components second. Get a single giant component compiling and rendering correctly before you start splitting it into pieces. Mixing conversion errors with refactoring errors makes both harder to find.
  • Hunt the inline handlers before you start. Search the source for onclick=, onchange=, and onsubmit=. Each one is a piece of behavior that needs a home in component state or props, and knowing the count up front tells you whether this is an afternoon or a week.
  • Decide what stays as raw HTML. Footers full of legal text, CMS-driven sections, and embed codes are often better left as dangerouslySetInnerHTML islands than converted line by line. Convert what will become interactive; embed what will not.
  • Diff the rendered output. After conversion, compare the rendered DOM against the original page. Whitespace collapse and dropped attributes show up immediately in a structural diff and almost never in a casual visual check.

Stop doing this by hand

Every transformation in this guide is mechanical, which means a tool should do it. Our free HTML to JSX Converter handles all of it: class to className, style strings to objects, self-closing void elements, attribute camelCasing including SVG, and comment conversion, instantly and entirely in your browser. Paste your markup, copy valid JSX, and spend your time on the part a tool cannot do: turning static markup into a living component. If you are cleaning up messy source before converting, run it through the HTML Formatter first so you can actually read what you are porting.

Keep reading

More practical notes from the same toolbox.

View all articles
Meta Tags That Actually Matter in 2026 (SEO and Social) cover art
HTML

Meta Tags That Actually Matter in 2026 (SEO and Social)

Which meta tags affect rankings, click-through, and social sharing in 2026, which are dead weight, and co…

6 min read Jun 6
HTML Email Coding: Why Your Beautiful CSS Breaks in Outlook cover art
HTML

HTML Email Coding: Why Your Beautiful CSS Breaks in Outlook

Why modern CSS fails in email clients, what Outlook actually renders with, and the table-based, inline-st…

6 min read May 26
How to Minify HTML (and Why It Matters for Core Web Vitals) cover art
HTML

How to Minify HTML (and Why It Matters for Core Web Vitals)

Learn what HTML minification actually removes, how much page weight it saves, and how it affects LCP and …

7 min read May 19

Put this into practice

Try the free HTML, CSS, JSON, and accessibility tools. No sign-up, no uploads, everything runs in your browser.

Open the toolbox