Skip to content
Code2026-07-265 min read

HTML to JSX Guide: Attributes, Styles, Events, and Comments

Why Convert HTML to JSX

React work often means embedding existing HTML: design exports, legacy fragments, or UI docs. Paste that markup into JSX and the console usually starts with:

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

JSX looks like HTML but follows JavaScript rules for attribute names and values. To skip hand-editing every prop, use the HTML to JSX converter: it rewrites className, camelCase attrs, style objects, events, comments, and void tags in one pass.

Core Differences Between HTML and JSX

1. class → className

class is reserved in JavaScript, so JSX uses className:

<!-- 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: string → object

In HTML, style is a string. In JSX it must be an object with camelCase CSS keys:

<!-- 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>

The double braces {{ }} are JSX expression braces around an object literal. Hyphenated properties (margin-top) must become marginTop, or React may ignore them.

3. for → htmlFor

The for attribute on <label> is renamed for the same reserved-word reason:

<!-- 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

HTML allows <br>, <img>, and <input> without a closer; JSX requires every tag to close. Use self-closing form when there are no children:

<!-- 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" />

charset becomes charSet. If the source already has <img />, a converter should not turn it into <img / />.

5. Event handlers

HTML events are lowercase strings; JSX uses camelCase and function expressions:

<button onclick="handleClick()">Click me</button>
<button onClick={handleClick}>Click me</button>
HTML attributeJSX attribute
onclickonClick
onchangeonChange
onsubmitonSubmit
onkeydownonKeyDown
onmouseoveronMouseOver
onfocusonFocus
onbluronBlur

A converter can rewrite onclick="handleClick()" to onClick={handleClick}, and wrap denser statements as () => { ... }. After pasting, confirm the function exists in scope and move global-script style calls onto props or hooks.

6. Other attribute renames

HTML attributeJSX attributeNotes
tabindextabIndexcamelCase
readonlyreadOnlycamelCase; boolean form without = included
maxlengthmaxLengthcamelCase
cellspacingcellSpacingcamelCase
colspan / rowspancolSpan / rowSpancamelCase
contenteditablecontentEditablecamelCase
crossorigincrossOrigincamelCase
autocompleteautoCompletecamelCase
autofocusautoFocuscamelCase
autoplayautoPlaycamelCase
acceptcharsetacceptCharsetcamelCase
defaultvaluedefaultValueuncontrolled inputs / textarea
defaultcheckeddefaultCheckeduncontrolled checkbox / radio

Multi-word HTML attributes almost always become camelCase in JSX.

7. SVG kebab-case attributes

SVG markup leans on stroke-width, fill-opacity, and clip-path. React DOM expects camelCase (strokeWidth, …); data-* / aria-* stay unchanged. When migrating icon snippets, rewrite path attributes too—not only the wrapping div.

Common Conversion Pitfalls

Comment syntax

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

Conditional comments and IE-only tags have no React equivalent—remove or rewrite them.

Boolean attributes

In HTML, presence is enough; in JSX you can pass booleans or use the bare attribute form:

<input disabled>
<input checked>
<input readonly>
<input disabled />
<input checked />
<input readOnly />

dangerouslySetInnerHTML

To inject raw HTML (rich text), do not set innerHTML directly:

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

The awkward name is intentional: treat it as an XSS reminder. Use only for trusted content, and sanitize untrusted input.

Limits of regex-based converters

Most online converters rewrite attributes; they are not full HTML parsers. Tidy fragments convert cleanly; Vue/Angular templates, broken markup, or bare > inside attribute values should be cleaned or formatted first. Whole static sites belong in gradual componentization, not one giant paste.

Uncontrolled defaults vs controlled React state

Design exports often ship <input value="…"> or <input checked>. In React, a literal value / checked without an onChange makes the field read-only (controlled with a frozen prop). Legacy HTML meant “initial value”; React means “always this value.”

HTML intentSafer JSX starting pointNotes
Initial textdefaultValue="…"Uncontrolled; DOM owns the value until you lift state
Initial checkeddefaultCheckedSame idea for checkbox / radio
Live form statevalue={x} + onChangeControlled; you own the source of truth

The converter renames defaultvalue / defaultchecked when the source already uses those HTML names. If the export used plain value / checked as initials, rename them yourself after paste—or wire controlled state before the first render. Pair with HTML formatter when nesting is hard to scan.

Fragments, multiple roots, and key

HTML snippets are often a list of sibling nodes without a wrapper. JSX historically needed one parent; today you can return <>…</> or React.Fragment. When mapping a list of converted nodes, React still needs a stable key on each top-level item—converters do not invent keys for you. Prefer wrapping a pasted card in one component root rather than dumping a flat sibling soup into a parent map.

Suggested workflow

  1. Optionally tidy nesting with the HTML formatter.
  2. Paste into HTML to JSX and review className, style, events, comments, and form defaults.
  3. Copy into a component, wire real handlers, and drop global-script calls.
  4. For docs, send readable copy through HTML ↔ Markdown when needed.

A solid converter covers class / for, style objects, void tags, camelCase props (including defaultValue / acceptCharset), events and simple handlers, comments, and common SVG kebab attrs. Business logic still needs manual component design.

Implementer's note

ToolVault’s converter is regex-oriented, not a browser HTML5 parser: it rewrites attributes, styles, events, comments, and void closers in a fixed pass order (comments protected so class= inside them is not mutated). It will not fix broken nesting, Vue/Angular directives, or > inside unquoted attribute values. That trade-off keeps the tool fast and fully local; treat output as a first draft you open in the component under TypeScript, not as a guaranteed DOM isomorphism.

Summary

A short checklist avoids most footguns:

  1. CamelCase attributes — className, htmlFor, onClick, charSet, …
  2. style is an object — camelCase CSS keys too
  3. Close every tag — void elements self-close without doubling / />
  4. Events are expressions — not HTML string scripts
  5. Comments change shape — <!-- --> → {/* */}

With a local converter, moving HTML into React is mostly attribute work; what remains is component boundaries and state—not renaming props by hand.