DevToys Web Pro iconDevToys Web ProBlogg
Översatt med LocalePack logoLocalePack
Betygsätt oss:
Prova webbläsartillägget:
Back to Blog

HTML to JSX: The Complete Conversion Guide for React Developers

9 min read

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.

HTMLJSXCategory
classclassNameReserved word
forhtmlForReserved word
tabindextabIndexcamelCase
contenteditablecontentEditablecamelCase
crossorigincrossOrigincamelCase
spellcheckspellCheckcamelCase
autocompleteautoCompletecamelCase
autofocusautoFocuscamelCase
autoplayautoPlaycamelCase
enctypeencTypecamelCase
onclickonClickEvent handler
onchangeonChangeEvent handler (behaviour differs)
onsubmitonSubmitEvent handler
stroke-widthstrokeWidthSVG / camelCase
fill-opacityfillOpacitySVG / camelCase
clip-pathclipPathSVG / camelCase
xmlns:xlinkxmlnsXlinkSVG 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. class and for are keywords in JavaScript (class MyComponent {}, for (let i ...) {}). Using them as JSX attribute names would break the parser. React chose className and htmlFor as 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-size becomes fontSize, background-color becomes backgroundColor, border-radius becomes borderRadius.
  • Numeric values. Pixel values can omit the unit: fontSize: 16 is equivalent to fontSize: '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 any data-* 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= with className=.
  • Replace for= on <label> with htmlFor=.
  • Close all void elements: <br />, <img />, <input />, <hr />, <meta />.
  • Convert style="..." strings to style={{ }} 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), convert style strings to objects, rename xmlns:xlink to xmlnsXlink.
  • Check onChange behaviour — use onBlur if you need blur-only semantics.
  • Sanitise any HTML rendered via dangerouslySetInnerHTML before 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.