Migrating HTML to React (JSX): A Developer's Guide
If you've written HTML before and are stepping into the React ecosystem, you'll notice something curious: JSX looks almost identical to HTML, but it isn't HTML. It's a syntax extension for JavaScript that lets you write markup directly inside your components. The resemblance is intentional — React's designers wanted JSX to feel familiar — but the differences, while few, are strict. Get one wrong and your component won't compile. This guide walks through every key difference between HTML and JSX, shows you how to transform your existing HTML into valid React components, and helps you avoid the pitfalls that catch most developers during their first migration.
Whether you're converting a static HTML site into a React application, migrating a legacy codebase, or simply learning React for the first time, this guide covers the attribute transformations, style syntax changes, self-closing tag rules, and event handler patterns you'll encounter. By the end, you'll be able to take any block of HTML and confidently rewrite it as clean, idiomatic JSX.
HTML vs JSX: Key Differences
At a glance, JSX and HTML look the same. But under the hood, JSX is JavaScript — and JavaScript has its own rules. Here are the core differences you need to know before converting a single line of markup:
- class becomes className — because
classis a reserved keyword in JavaScript, JSX usesclassNamefor CSS classes. - for becomes htmlFor — similarly,
foris reserved in JavaScript (it's a loop keyword), so label elements usehtmlForinstead. - Inline styles are objects, not strings — instead of
style="color: red", you writestyle={{color: "red"}}. - Self-closing tags are mandatory — HTML lets you write
<br>or<img src="...">, but JSX requires<br />and<img src="..." />. - Attributes are camelCase — multi-word HTML attributes like
tabindex,maxlength, andreadonlybecometabIndex,maxLength, andreadOnly. - Event handlers are camelCase too —
onclickbecomesonClick,onchangebecomesonChange, and they take function references instead of strings.
Attribute Transformations
The most common task in an HTML-to-JSX migration is renaming attributes. JSX follows the DOM property naming convention rather than the HTML attribute naming convention. In practice, this means every attribute that contains a hyphen or is a reserved word in JavaScript needs to be renamed. Let's look at the most important transformations with before-and-after examples.
class to className: This is the single most common change you'll make. Every HTML element with a class attribute must be updated:
<!-- HTML --> <div class="container main-wrapper"> <p class="intro-text">Hello world</p> </div> // JSX <div className="container main-wrapper"> <p className="intro-text">Hello world</p> </div>
for to htmlFor: The for attribute on <label> elements links the label to a form control. In JSX, this becomes htmlFor:
<!-- HTML --> <label for="email">Email address</label> <input type="email" id="email"> // JSX <label htmlFor="email">Email address</label> <input type="email" id="email" />
Other camelCase conversions: Here is a reference list of commonly used attributes and their JSX equivalents:
tabindex→tabIndexmaxlength→maxLengthreadonly→readOnlycolspan→colSpanrowspan→rowSpanenctype→encTypeaccesskey→accessKeycontenteditable→contentEditablecrossorigin→crossOrigin
The pattern is consistent: if an HTML attribute is a single lowercase word, it stays the same (like id, src, href). If it's a compound word, it becomes camelCase. And if it clashes with a JavaScript reserved word, it gets a prefix like html or uses className.
Inline Styles: Strings to Objects
In HTML, the style attribute takes a CSS string. In JSX, it takes a JavaScript object. This is one of the most error-prone differences because it changes both the syntax and the property naming convention. Here's a direct comparison:
<!-- HTML -->
<div style="color: red; font-size: 14px; background-color: #f0f0f0; margin-top: 20px;">
Styled content
</div>
// JSX
<div style={{color: "red", fontSize: "14px", backgroundColor: "#f0f0f0", marginTop: "20px"}}>
Styled content
</div>Notice the three changes happening at once:
- Double curly braces — the outer pair
{ }is the JSX expression delimiter (like any JavaScript expression in JSX), and the inner pair is the JavaScript object literal. - camelCase property names — every hyphenated CSS property becomes camelCase:
font-size→fontSize,background-color→backgroundColor,margin-top→marginTop,z-index→zIndex. - Values are strings or numbers — string values stay as strings in quotes. Numeric values can optionally drop the quotes and the
pxunit: you can writefontSize: 14and React will automatically appendpx. However, this only works for properties that accept pixel values — not forzIndex,opacity, orflex, which are unitless.
Common CSS-to-JSX style conversions
font-weight→fontWeighttext-align→textAlignborder-radius→borderRadiusbox-shadow→boxShadowtext-decoration→textDecorationlist-style-type→listStyleType
A useful mental model: take any CSS property name, remove the hyphens, and capitalize the letter that followed each hyphen. That gives you the JSX property name every time.
Self-Closing Tags
HTML is forgiving with void elements — tags that don't have children. You can write <br>, <hr>, or <img src="photo.jpg"> and browsers will render them correctly. JSX is not so lenient. Because JSX is parsed as JavaScript expressions, every opening tag must either have a corresponding closing tag or be explicitly self-closed with a trailing slash.
Here are the HTML void elements that must be self-closed in JSX:
<!-- HTML (valid) --> <br> <hr> <img src="photo.jpg" alt="A photo"> <input type="text" name="email"> <meta charset="UTF-8"> <link rel="stylesheet" href="styles.css"> // JSX (required self-closing) <br /> <hr /> <img src="photo.jpg" alt="A photo" /> <input type="text" name="email" /> <meta charSet="UTF-8" /> <link rel="stylesheet" href="styles.css" />
Note two things in the example above: first, every void element now has a trailing />. Second, charset became charSet — the camelCase rule applies everywhere, including meta attributes.
This rule also extends to custom components. If a React component doesn't take children, you should self-close it: <UserAvatar name="Alex" /> rather than <UserAvatar name="Alex"></UserAvatar>. While both forms are valid JSX, the self-closing form is the convention in the React community.
Event Handlers in JSX
HTML event handlers are lowercase strings that contain JavaScript code. JSX event handlers are camelCase props that take function references. This is a fundamental shift in how you wire up interactivity:
<!-- HTML -->
<button onclick="handleClick()">Click me</button>
<input type="text" onchange="handleChange()" onfocus="handleFocus()">
<form onsubmit="handleSubmit(); return false;">
// JSX
<button onClick={handleClick}>Click me</button>
<input type="text" onChange={handleChange} onFocus={handleFocus} />
<form onSubmit={handleSubmit}>The key differences to note:
- camelCase naming —
onclick→onClick,onchange→onChange,onsubmit→onSubmit,onmouseover→onMouseOver. - Function references, not strings — in HTML, you pass a string of code:
onclick="doSomething()". In JSX, you pass a reference to a function:onClick={doSomething}. Notice there are no parentheses after the function name — you're passing the function itself, not calling it. - Inline arrow functions — when you need to pass arguments or execute multiple statements, use an inline arrow function:
onClick={() => handleDelete(id)}. This creates a new function on each render, which is fine for most cases but worth noting for performance-critical lists. - No return false — in HTML, you can write
onsubmit="return false"to prevent form submission. In React, you calle.preventDefault()on the synthetic event object that React passes to your handler.
// Preventing default behavior in React
function ContactForm() {
function handleSubmit(e) {
e.preventDefault();
// process form data
}
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" />
<button type="submit">Send</button>
</form>
);
}Thinking in Components
Migrating HTML to React is about more than swapping attributes. The real power of React comes from breaking your page into reusable, composable components. A typical HTML page is one large file with sections for the header, navigation, main content, sidebar, and footer. In React, each of those sections becomes its own component with its own file, its own state, and its own logic.
Here's how to think about decomposing an HTML page:
- Identify repeated patterns — if you see the same card structure appearing five times with different data, that's a component. Create a
Cardcomponent that acceptstitle,description, andimageas props. - Identify logical sections — the header, footer, sidebar, and main content area are natural component boundaries. Each one encapsulates its own markup, styles, and behavior.
- Think about data flow — parent components pass data down to children via props. If a sidebar needs to display the user's name, the parent component fetches that data and passes it down:
<Sidebar userName="Alex" />. - Start with a flat structure — don't over-componentize on your first pass. Start by copying your HTML into a single React component, fix all the JSX syntax issues, and then gradually extract subcomponents as patterns emerge.
// Before: one monolithic HTML page
// After: component hierarchy
function App() {
return (
<>
<Header />
<main>
<HeroSection title="Welcome" />
<FeaturesGrid features={featureList} />
<Testimonials />
</main>
<Footer />
</>
);
}Each of these components manages its own slice of the UI. The FeaturesGrid component receives an array of features via props and renders a card for each one. The HeroSection takes a title prop and renders the hero banner. This composability is what makes React applications maintainable at scale — each component is small, testable, and reusable.
Common Migration Gotchas
Even after you've learned the core differences, there are several edge cases and subtle issues that catch developers during migration. Here are the ones to watch for:
- className on custom components — when you write
<MyButton className="primary" />, theclassNameis just a prop — it won't automatically apply to the rendered DOM element. TheMyButtoncomponent must explicitly pass it through:<button className={props.className}>...</button>. - dangerouslySetInnerHTML — if your HTML includes server-rendered content that must be inserted as raw HTML, React requires you to use the
dangerouslySetInnerHTMLprop with a__htmlkey. The deliberately verbose name is a reminder that this bypasses React's XSS protections:<div dangerouslySetInnerHTML={{__html: rawContent}} />. - Conditional rendering — HTML doesn't have conditional rendering built in (unless you count template engines). In React, you use JavaScript expressions directly in JSX. Common patterns include the logical AND operator (
{isLoggedIn && <Dashboard />}), ternary expressions ({isAdmin ? <AdminPanel /> : <UserPanel />}), and early returns within components. - SVG attribute differences — SVGs have their own set of attribute name changes in JSX. The most common ones:
stroke-width→strokeWidth,fill-rule→fillRule,clip-path→clipPath,font-size→fontSize. SVG namespaced attributes likexlink:hrefbecomexlinkHref(thoughxlink:hrefis deprecated in favor of plainhrefin modern SVG). - Boolean attributes — in HTML, you write
<input disabled>or<input disabled="disabled">. In JSX, you write<input disabled />(which is shorthand fordisabled={true}) or<input disabled={false} />to remove the attribute. Don't writedisabled="false"— the string"false"is truthy in JavaScript, so the input will still be disabled. - Fragments for multiple root elements — HTML templates can return multiple sibling elements. JSX expressions must have a single root. Wrap siblings in a
<React.Fragment>or its shorthand<>...</>to avoid adding extra DOM nodes.
Using Conversion Tools
Manually renaming every class to className, every for to htmlFor, and every inline style from a string to an object is tedious and error-prone — especially when you're migrating hundreds of lines of HTML. This is exactly the kind of mechanical, repetitive transformation where automated tools shine.
A good HTML-to-JSX converter handles all of the transformations described in this guide automatically: attribute renaming, style object conversion, self-closing tag enforcement, and event handler formatting. You paste your HTML, click convert, and get valid JSX that you can drop directly into a React component. This lets you focus on the higher-level work — structuring components, managing state, and building interactivity — instead of mechanical syntax changes.
After converting, it's also a good practice to compare your original HTML with the JSX output side by side. A diff checker tool can highlight every change that was made, so you can verify the conversion was accurate and learn from the specific transformations applied to your code.
Conversion tools are not a replacement for understanding the rules. You'll still need to know why className is used instead of class — because you'll be writing new JSX from scratch every day, and the converter only helps with existing HTML. But for the initial migration of a large codebase, they can save hours of manual work and eliminate entire categories of typo-related bugs.
Convert HTML to JSX Instantly
Paste any HTML and get valid, ready-to-use JSX. Automatically converts class to className, for to htmlFor, inline styles to objects, self-closes void elements, and applies camelCase to all attributes — processed entirely in your browser.
Key Takeaways
- JSX is JavaScript, not HTML — reserved words like
classandformust be replaced withclassNameandhtmlFor. All multi-word attributes and event handlers use camelCase. - Inline styles change from CSS strings to JavaScript objects with camelCase property names. The double curly brace syntax (
style={{}}) is a JSX expression containing an object literal, not special syntax. - All void elements (
<br>,<img>,<input>,<hr>) must be self-closed with a trailing slash in JSX. This applies to custom components without children too. - Event handlers take function references, not strings. Use
onClick={handleClick}instead ofonclick="handleClick()", and calle.preventDefault()instead ofreturn false. - Breaking HTML into React components is the real migration — not just syntax changes. Identify repeated patterns, extract logical sections, and pass data through props to build a maintainable component hierarchy.
- Watch for gotchas: className on custom components doesn't auto-apply, boolean attributes behave differently,
"false"as a string is truthy, and SVG attributes have their own camelCase mappings. - Use an HTML-to-JSX converter for bulk migrations to eliminate mechanical errors, then use a diff checker to verify every change. Learn the rules so you can write clean JSX from scratch going forward.
Try These Free Tools
Related Articles
5 Free Online Tools Every Developer Needs
Discover the essential free online tools that every developer should bookmark — from JSON formatting and regex testing to Base64 encoding and UUID generation.
How to Optimize Images for the Web
A practical guide to image optimization — learn about compression, resizing, format selection, and free tools to make your website load faster.
How to Compress Images Without Losing Quality
Learn the difference between lossy and lossless compression, the best quality settings for JPEG, PNG, and WebP, and how to shrink image files without visible quality loss.