Skip to content
code2026-07-264 min read

Why Convert HTML to JSX

In React development, we frequently need to embed existing HTML code into components. It could be HTML exported from design tools, page fragments copied from other projects, or examples taken from UI component library documentation.

Pasting HTML directly into a React component will almost certainly throw errors. The most common one is:

Skip the manual rewrite — our HTML to JSX converter handles className, camelCase props, and self-closing tags for you.

Warning: Invalid DOM property `class`. Did you mean `className`?

This happens because although JSX looks like HTML, it's essentially a syntax extension of JavaScript with its own set of rules. HTML attribute names and JSX attribute names don't always match.

Core Differences Between HTML and JSX

1. class → className

This is the most well-known difference. Since class is a reserved word in JavaScript, you must use className in JSX instead:

<!-- HTML -->
<div class="container card shadow">
  <p class="text-sm text-gray-600">Hello</p>
</div>
{/* JSX */}
<div className="container card shadow">
  <p className="text-sm text-gray-600">Hello</p>
</div>

2. style Attribute Changes from String to Object

In HTML, style is a string. In JSX, style must be an object with camelCase property names:

<!-- HTML -->
<div style="margin-top: 20px; background-color: #f0f0f0; font-size: 14px;">
  content
</div>
{/* JSX */}
<div style={{ marginTop: '20px', backgroundColor: '#f0f0f0', fontSize: '14px' }}>
  content
</div>

Notice the double curly braces {{ }} — the outer pair is JSX expression syntax, and the inner pair is the object literal.

3. for → htmlFor

The for attribute on <label> tags also needs to be renamed, for the same reason as class:

<!-- HTML -->
<label for="email">Email</label>
<input id="email" type="email">
{/* JSX */}
<label htmlFor="email">Email</label>
<input id="email" type="email" />

4. Self-Closing Tags

In HTML, many tags don't require a closing tag (like <br>, <img>, <input>), but JSX requires all tags to be closed. Tags without children must use self-closing syntax:

<!-- HTML -->
<img src="logo.png" alt="logo">
<br>
<input type="text" placeholder="Enter name">
<meta charset="utf-8">
{/* JSX */}
<img src="logo.png" alt="logo" />
<br />
<input type="text" placeholder="Enter name" />
<meta charSet="utf-8" />

As a side note, charset also becomes charSet.

5. Event Handlers

In HTML, event attributes are all lowercase with string values containing code:

<button onclick="handleClick()">Click me</button>

In JSX, event names use camelCase and values are function references:

<button onClick={handleClick}>Click me</button>

Common mappings:

| HTML Attribute | JSX Attribute | |----------------|---------------| | onclick | onClick | | onchange | onChange | | onsubmit | onSubmit | | onkeydown | onKeyDown | | onmouseover | onMouseOver | | onfocus | onFocus | | onblur | onBlur |

6. Other Attribute Name Differences

There are several other attributes with different names in JSX:

| HTML Attribute | JSX Attribute | Notes | |----------------|---------------|-------| | tabindex | tabIndex | camelCase | | readonly | readOnly | camelCase | | maxlength | maxLength | camelCase | | cellspacing | cellSpacing | camelCase | | cellpadding | cellPadding | camelCase | | colspan | colSpan | camelCase | | rowspan | rowSpan | camelCase | | usemap | useMap | camelCase | | contenteditable | contentEditable | camelCase | | crossorigin | crossOrigin | camelCase | | datetime | dateTime | camelCase (time tag) | | autocomplete | autoComplete | camelCase | | autofocus | autoFocus | camelCase | | autoplay | autoPlay | camelCase |

Essentially, all multi-word HTML attributes need to be converted to camelCase in JSX.

Common Conversion Pitfalls

Different Comment Syntax

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

this in Inline Event Handlers

In HTML, onclick="foo()" calls a global function. In JSX, you need to be careful about this binding — usually handled with arrow functions or bind.

Boolean Attributes

In HTML, certain attributes take effect just by being present (like disabled, checked, readonly). In JSX, you need to explicitly pass boolean values:

<!-- HTML -->
<input disabled>
<input checked>
{/* JSX */}
<input disabled={true} />
<input checked={true} />
{/* or more concisely */}
<input disabled />
<input checked />

dangerouslySetInnerHTML

If you really need to set HTML content directly (for example, rendering rich text), you can't just use innerHTML. You must use dangerouslySetInnerHTML:

<div dangerouslySetInnerHTML={{ __html: '<p>Raw HTML content</p>' }} />

React intentionally made this API look "ugly" to remind you: directly injecting HTML carries XSS risk, use it with caution.

Tool Recommendations

Manual HTML-to-JSX conversion is tedious and error-prone. For complex HTML fragments, automated tools are recommended:

  • HTML to JSX online converter: paste HTML and get back properly formatted JSX with one click
  • VS Code extensions: some extensions can handle conversion right in the editor
  • AI tools like ChatGPT / Claude: for particularly complex structures, AI can handle things more intelligently

A good converter should automatically handle:

  • classclassName
  • style string → object
  • forhtmlFor
  • self-closing tag completion
  • camelCase attribute names
  • event name conversion

Summary

HTML to JSX conversion seems simple but has many details. Remember a few core principles to avoid most pitfalls:

  1. Use camelCase for attributesclassName, htmlFor, onClick, etc.
  2. style is an object — not a string
  3. All tags must close — use self-closing when there are no children
  4. JS expressions use {} — instead of string interpolation in HTML

Master these rules, combine them with the right tools, and HTML-to-JSX conversion will no longer be a hassle.


Advertisement