Why HTML does not paste straight into React
JSX looks like HTML but follows JavaScript rules, so markup copied from a Bootstrap example, an email template, an icon set or a designer’s export breaks in a dozen small ways: class is a reserved word, style must be an object, every tag must be closed, and attribute names are camelCase. Fixing them by hand is tedious and error-prone. This converter rewrites the markup into a component — export default function Component() returning the JSX — formatted with Prettier, ready to rename and drop into your project.
What gets rewritten
- Attribute names:
classbecomesclassName,forbecomeshtmlFor, and HTML attributes React spells in camelCase are renamed —tabindex→tabIndex,maxlength→maxLength,readonly→readOnly,colspan→colSpan,autofocus→autoFocus.data-*andaria-*attributes are kept as written. - Inline styles become style objects:
style="margin-top: 8px; background-color: #fff"turns intostyle={{ marginTop: '8px', backgroundColor: '#fff' }}. Vendor prefixes follow React’s rules (-webkit-→Webkit…,-ms-→ms…), and CSS custom properties keep their--name. - Void elements such as
<br>,<img>and<input>are self-closed, and empty elements become<div />. - Boolean attributes like
required,disabledorautofocusare written bare. - Comments become JSX comments:
{/* note */}. - Text has its whitespace collapsed as a browser would display it;
{and}are escaped, while entities such as&are kept. Inside<pre>, text is emitted as string literals so every space and line break survives. - Several top-level elements are wrapped in a fragment
<>…</>, since a component returns a single root.
Malformed HTML is parsed the way a browser would: implied end tags for <p>, <li> and table cells are added, and stray closing tags are ignored.
Events, forms, SVG and scripts
Event handlers: onclick="open()" becomes onClick={() => { … open(); }} with a TODO comment, because inline handler strings run in a different scope than React handlers. If the code uses this, the comment reminds you to use event.currentTarget; if it is not valid JavaScript, it is kept as a comment with a warning.
Form fields stay uncontrolled: value on inputs, selects and textareas becomes defaultValue, checked becomes defaultChecked, and <textarea> content moves into defaultValue. Otherwise React would render them read-only. Note that checked="false" still means checked in HTML, so it stays on, with an info message.
SVG keeps its case-sensitive names (viewBox), and hyphenated presentation attributes are camelCased: stroke-width → strokeWidth, xlink:href → xlinkHref. <script> and <style> bodies are passed through dangerouslySetInnerHTML so their content is not mangled.
Limits
The doctype is dropped, !important cannot be expressed in React inline styles and is removed with a warning, and attributes whose names are not valid in JSX are passed through an object spread. The converter does not create state, props or separate components, and <option selected> is kept as is, though React prefers defaultValue on the <select>. Check the output with the JavaScript formatter or tidy the source first with the HTML formatter. Nothing leaves your browser.
Examples
Card with styles and a click handler
class, for and tabindex are renamed, the style string becomes an object, void tags are self-closed and the comment becomes a JSX comment.
<div class="card" style="margin-top: 8px; background-color: #fff" onclick="openCard()">
<label for="email">Email</label>
<input id="email" type="email" required autofocus>
<br>
<img src="/logo.png" alt="Logo">
<!-- note -->
<p tabindex="0">Hi & welcome</p>
</div>export default function Component() {
return (
<div
className="card"
style={{ marginTop: '8px', backgroundColor: '#fff' }}
onClick={() => {
// TODO: port this inline onclick handler
openCard();
}}
>
<label htmlFor="email">Email</label>
<input id="email" type="email" required autoFocus />
<br />
<img src="/logo.png" alt="Logo" />
{/* note */} <p tabIndex="0">Hi & welcome</p>
</div>
);
}
Inline SVG icon
viewBox keeps its case, the hyphenated stroke attributes become strokeWidth, strokeLinecap and strokeLinejoin, and empty shapes self-close.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>export default function Component() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
);
}
Form fragment with values
value and checked become defaultValue and defaultChecked, and the textarea content moves into defaultValue, keeping the fields editable in React.
<form>
<input type="text" name="q" value="keyboard">
<input type="checkbox" name="instock" checked>
<textarea name="note">Gift wrap, please</textarea>
<button type="submit" disabled>Search</button>
</form>export default function Component() {
return (
<form>
<input type="text" name="q" defaultValue="keyboard" />
<input type="checkbox" name="instock" defaultChecked />
<textarea name="note" defaultValue={'Gift wrap, please'} />
<button type="submit" disabled>
Search
</button>
</form>
);
}
Common errors and how to fix them
| Error | Cause | Fix |
|---|---|---|
The onclick handler is not valid JavaScript; it is kept as a comment | A warning: the inline handler string contains a syntax error or a template placeholder. | Rewrite the handler as a function in your component and reference it with onClick={handleClick}. |
React inline styles cannot be !important; the flag is dropped | A warning: an inline style uses !important, which React style objects cannot express. | Move that rule into a CSS class if the priority matters. |
The tag <my:widget> is not a valid JSX element name; it is written as <mywidget> | A warning: the markup uses a tag name with characters JSX does not allow. | Check the renamed element and replace it with the component or element you intended. |
The doctype is not part of JSX and was dropped | An info note: a full HTML document was pasted, including <!DOCTYPE html>. | Usually paste only the body content; html, head and body tags rarely belong in a component. |
Frequently asked questions
Why does class become className?
class is a reserved word in JavaScript, so React uses className for the HTML class attribute. Similarly, for becomes htmlFor.
Why are value and checked turned into defaultValue and defaultChecked?
In React, value and checked make an input controlled, which means it cannot be typed into without an onChange handler. The default props keep the field behaving like the original HTML.
Does it work for Preact, Solid or Next.js?
The output is standard React JSX, so it works in Next.js and Preact (with compat). Solid accepts class and lower-case events, so some renames are unnecessary there but harmless.
What happens to inline event handlers?
They become arrow functions containing the original code and a TODO comment. Review them, since this and the global scope differ from inline HTML handlers.
Is my HTML uploaded?
No. Parsing, rewriting and formatting all happen in your browser.