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 attribute | JSX attribute |
|---|---|
onclick | onClick |
onchange | onChange |
onsubmit | onSubmit |
onkeydown | onKeyDown |
onmouseover | onMouseOver |
onfocus | onFocus |
onblur | onBlur |
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 attribute | JSX attribute | Notes |
|---|---|---|
tabindex | tabIndex | camelCase |
readonly | readOnly | camelCase; boolean form without = included |
maxlength | maxLength | camelCase |
cellspacing | cellSpacing | camelCase |
colspan / rowspan | colSpan / rowSpan | camelCase |
contenteditable | contentEditable | camelCase |
crossorigin | crossOrigin | camelCase |
autocomplete | autoComplete | camelCase |
autofocus | autoFocus | camelCase |
autoplay | autoPlay | camelCase |
acceptcharset | acceptCharset | camelCase |
defaultvalue | defaultValue | uncontrolled inputs / textarea |
defaultchecked | defaultChecked | uncontrolled 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 intent | Safer JSX starting point | Notes |
|---|---|---|
| Initial text | defaultValue="…" | Uncontrolled; DOM owns the value until you lift state |
| Initial checked | defaultChecked | Same idea for checkbox / radio |
| Live form state | value={x} + onChange | Controlled; 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
- Optionally tidy nesting with the HTML formatter.
- Paste into HTML to JSX and review className, style, events, comments, and form defaults.
- Copy into a component, wire real handlers, and drop global-script calls.
- 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:
- CamelCase attributes —
className,htmlFor,onClick,charSet, … - style is an object — camelCase CSS keys too
- Close every tag — void elements self-close without doubling
/ /> - Events are expressions — not HTML string scripts
- 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.
Related Tools
Related Articles
px, rem, em: What Each CSS Unit Actually Answers, and How to Convert Without Surprises
The boundary between px, rem, and em (root-relative vs parent-relative), the px-to-rem formula and size table, the 62.5% trick's hidden costs, the Safari media-query rem trap, and browser minimum font size — with an online converter.
Online CSS Minifier: Reduce CSS File Size for Better Performance
Learn how to minify CSS safely: whitespace, comments, hex shortening, zero-unit elision, calc protection, and UTF-8 byte savings — with an online tool you can use locally.
Regex Not Matching? Troubleshoot These 6 Common Pitfalls
How to troubleshoot when a regex doesn't match or matching fails? This article walks through 6 common pitfalls — greedy quantifiers, missing flags, unescaped special chars, lookaround assertions, newline handling, and Unicode properties — with before/after examples to help you quickly locate regex debugging issues.