HTML to JSX: The Complete Conversion Guide for React Developers
You copy a block of HTML from a design file or a Stack Overflow answer, paste it into a React component, and immediately get a wall of ESLint errors. class is not a valid prop. for is a reserved word. <br> needs to be closed. onclick should be onClick. The list goes on. Use the HTML to JSX Converter to automate the tedious parts, and keep this guide open for understanding why each change is required.
What Changes: The Attribute Rename Reference
The table below covers every attribute that is spelled differently in JSX compared to HTML. Bookmark it — you will come back to it.
| HTML | JSX | Category |
|---|---|---|
class | className | Reserved word |
for | htmlFor | Reserved word |
tabindex | tabIndex | camelCase |
contenteditable | contentEditable | camelCase |
crossorigin | crossOrigin | camelCase |
spellcheck | spellCheck | camelCase |
autocomplete | autoComplete | camelCase |
autofocus | autoFocus | camelCase |
autoplay | autoPlay | camelCase |
enctype | encType | camelCase |
onclick | onClick | Event handler |
onchange | onChange | Event handler (behaviour differs) |
onsubmit | onSubmit | Event handler |
stroke-width | strokeWidth | SVG / camelCase |
fill-opacity | fillOpacity | SVG / camelCase |
clip-path | clipPath | SVG / camelCase |
xmlns:xlink | xmlnsXlink | SVG namespace |
Why the Renames Exist
React renders to the DOM using a JavaScript API — element.className, element.tabIndex, and so on. JSX attributes map directly to DOM properties, not to HTML attribute strings.
Two categories drive most of the differences:
- Reserved words.
classandforare keywords in JavaScript (class MyComponent {},for (let i ...) {}). Using them as JSX attribute names would break the parser. React choseclassNameandhtmlForas the unambiguous equivalents. - Property vs attribute distinction. HTML attributes are lowercase strings in the markup (
tabindex="0"). DOM properties use camelCase (element.tabIndex). JSX follows the DOM property convention, so every multi-word attribute name becomes camelCase.
This design choice means JSX is closer to the DOM API than to HTML — which matters when you read or write React internals, but for everyday component work, the table above covers 95% of what you will encounter.
Self-Closing Tags
HTML is lenient about void elements. You can write <br>, <hr>, <img src="...">, and browsers parse them correctly. JSX is XML-based and requires every element to be explicitly closed:
<!-- HTML: these are valid -->
<br>
<hr>
<img src="photo.jpg" alt="A photo">
<input type="text">{/* JSX: every void element must self-close */}
<br />
<hr />
<img src="photo.jpg" alt="A photo" />
<input type="text" />The rule applies to every element with no children: <meta />, <link />, <source />, <track />, <wbr />. Forgetting the slash is one of the most common paste errors — the HTML to JSX Converter handles it automatically.
The Style Attribute: Strings to Objects
In HTML, style is a plain string. In JSX it is a JavaScript object with camelCase property names:
<!-- HTML -->
<div style="color: red; font-size: 16px; background-color: #fff;">
Hello
</div>{/* JSX */}
<div style={{ color: 'red', fontSize: 16, backgroundColor: '#fff' }}>
Hello
</div>Three things to remember:
- Double braces. The outer
{}is JSX expression syntax. The inner{}is the JavaScript object literal. - camelCase properties.
font-sizebecomesfontSize,background-colorbecomesbackgroundColor,border-radiusbecomesborderRadius. - Numeric values. Pixel values can omit the unit:
fontSize: 16is equivalent tofontSize: '16px'. Non-pixel values always need the unit as a string:lineHeight: '1.5em'.
Comments
HTML comments do not exist in JSX. Replace them with JavaScript block comments inside a JSX expression:
<!-- HTML comment -->{/* JSX comment */}Single-line // comments also work inside expressions, but only on their own line — they cannot appear inline next to JSX attributes.
Boolean Attributes
In HTML, the presence of an attribute implies true: <input disabled>. In JSX you can write either form:
{/* These are equivalent */}
<input disabled />
<input disabled={true} />
{/* Explicitly false — omit the attribute or write: */}
<input disabled={false} />The bare form (disabled without a value) is idiomatic React and what most linters expect. The {true} form is useful when the value is dynamic: disabled={isSubmitting}.
Note on checked and value: For controlled inputs, these become React state props. defaultChecked and defaultValue are the uncontrolled equivalents that match the HTML mental model of setting an initial value.
SVG Conversion
Pasting SVGs from design tools (Figma, Illustrator, SVGOMG output) into JSX requires the most changes. Every hyphenated attribute name becomes camelCase, inline style strings become objects, and namespace prefixes become camelCase identifiers:
<!-- SVG from a design tool -->
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24">
<path
stroke-width="2"
stroke-linecap="round"
fill-opacity="0.5"
clip-path="url(#clip)"
style="stroke: #333; fill: none;"
d="M5 12h14"
/>
<defs>
<clipPath id="clip">
<rect width="24" height="24" />
</clipPath>
</defs>
</svg>{/* Same SVG in JSX */}
<svg xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24">
<path
strokeWidth={2}
strokeLinecap="round"
fillOpacity={0.5}
clipPath="url(#clip)"
style={{ stroke: '#333', fill: 'none' }}
d="M5 12h14"
/>
<defs>
<clipPath id="clip">
<rect width={24} height={24} />
</clipPath>
</defs>
</svg>A complete working React icon component wrapping the converted SVG:
interface IconProps {
size?: number;
color?: string;
}
export function ArrowIcon({ size = 24, color = '#333' }: IconProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width={size}
height={size}
aria-hidden="true"
>
<path
strokeWidth={2}
strokeLinecap="round"
style={{ stroke: color, fill: 'none' }}
d="M5 12h14M13 6l6 6-6 6"
/>
</svg>
);
}Event Handlers: onChange vs onInput
HTML event attributes are lowercase strings containing JavaScript: onclick="handleClick()". In JSX they are camelCase props that accept a function reference:
<!-- HTML -->
<button onclick="handleClick()">Click me</button>
<input onchange="handleChange(this.value)">{/* JSX */}
<button onClick={handleClick}>Click me</button>
<input onChange={(e) => handleChange(e.target.value)} />There is one important behavioural difference: React's onChange fires on every keystroke (like the native input event), not only when the field loses focus (like the native change event). This is intentional — it enables controlled inputs to stay in sync with state on every character.
If you are porting vanilla JavaScript that relies on the native change event firing only on blur, use React's onBlur instead. If you need the raw DOM input event for exact parity with non-React code, onInput is available in React as well.
When the Converter Is Not Enough
The HTML to JSX Converter handles all mechanical transforms. There are four categories of HTML that require manual decisions after conversion:
- Data attributes.
data-id="42",data-testid="submit"— these pass through unchanged. React forwards anydata-*attribute to the DOM without modification. - ARIA attributes.
aria-label,aria-hidden,aria-describedby— also pass through unchanged, hyphens and all. React treats them as-is. See the Text Tools Guide for accessibility copy patterns. - Custom elements. Web components with custom tag names (
<my-button>,<ion-card>) work in JSX. Unknown attributes are forwarded to the DOM as strings. - Untrusted HTML. If you need to render server-generated or user-provided HTML, the only JSX mechanism is
dangerouslySetInnerHTML. Always sanitise the input with a dedicated library before passing it — never inject unsanitised strings.
Migration Checklist
Use this checklist every time you paste HTML into a React component:
- Run the snippet through the HTML to JSX Converter first.
- Replace
class=withclassName=. - Replace
for=on<label>withhtmlFor=. - Close all void elements:
<br />,<img />,<input />,<hr />,<meta />. - Convert
style="..."strings tostyle={{ }}objects with camelCase property names. - Replace
<!-- -->HTML comments with{ /* */ }. - camelCase all event handlers:
onclick→onClick,onsubmit→onSubmit. - For SVGs: camelCase every hyphenated attribute (
stroke-width→strokeWidth), convertstylestrings to objects, renamexmlns:xlinktoxmlnsXlink. - Check
onChangebehaviour — useonBlurif you need blur-only semantics. - Sanitise any HTML rendered via
dangerouslySetInnerHTMLbefore injection.
The HTML to JSX Converter automates all the mechanical steps in this checklist — paste your HTML, copy the JSX, and skip the error cycle entirely.