HandyBench

HTML to JSX Converter

Convert HTML markup into React JSX.

How to use the HTML to JSX Converter

  1. 1 Paste your HTML markup.
  2. 2 Optionally wrap the result in a React component.
  3. 3 Click Convert and copy the JSX.

What changes between HTML and JSX

JSX looks like HTML but is really JavaScript, so a few rules differ. This converter parses your markup with the browser's own HTML parser (DOMParser), walks the resulting nodes, and prints each one back out in JSX form with two-space indentation. Because it uses a real parser instead of regular expressions, unclosed tags and missing quotes are repaired the same way a browser would repair them.

HTMLJSX outputReason
class, forclassName, htmlForReserved words in JavaScript
tabindex, colspan, maxlength, readonly, srcset and similartabIndex, colSpan, maxLength, readOnly, srcSetReact uses camelCase DOM property names
stroke-width and other hyphenated namesstrokeWidthHyphens are camelCased, except data-* and aria-*, which stay as they are
style="margin-top: 4px"style={{ marginTop: '4px' }}React wants an object, not a string
<br>, <img>, <input> and other void tagsSelf-closed with />JSX requires every tag to close
<!-- note -->{/* note */}HTML comments are not valid JSX
xlink:href, xml:lang, xmlns:xlinkxlinkHref, xmlLang, xmlnsXlinkNamespaced attributes are camelCased
onclick="go()", onmouseover="..."onClick={() => { ... go() }}, onMouseOver={...}React needs a function, not a string

Worked example

Input:

<div class="card" style="background-color: red; margin-top:4px">
  <label for="email">Email</label>
  <input type="email" id="email" tabindex="1" disabled>
  <!-- note -->
  <button onclick="go()">Go</button>
</div>

Output:

<div className="card" style={{ backgroundColor: 'red', marginTop: '4px' }}>
  <label htmlFor="email">Email</label>
  {' '}
  <input type="email" id="email" tabIndex="1" disabled />
  {/* note */}
  <button onClick={() => {
    /* inline handler from HTML: check how it used this and event */
go()
  }}>Go</button>
</div>

What you still have to fix by hand

  • Event handlers need a review. An inline handler such as onclick="go()" is wrapped in a function, onClick={() => { ... }}, so the JSX is valid, and the original code is kept inside it on its own lines, so even a handler that ends with a // comment cannot swallow the closing brace. Inline handlers ran with this set to the element and an event variable in scope; in React the first is not set and the second is a parameter you add ((event) => ...). Replace the stub with a proper handler such as onClick={go} once you have moved the function into your component.
  • Spaces between inline elements are kept. Where the HTML has a space between text and an inline element (Hello <b>world</b> again), the converter writes it as {' '} so it still renders. Whitespace between block elements, such as line breaks and indentation, is dropped, as it is in HTML. Text inside <pre> loses its line breaks and indentation, so wrap such text in a string yourself.
  • Special characters in text are escaped. {, }, < and > in text become {'{'}, {'}'}, {'<'} and {'>'}, and an entity-like text such as a literal &amp; is protected. Entities such as &nbsp; are decoded by the parser and printed as the raw character.
  • Attribute values with double quotes are written as a JavaScript string, title={"say \"hi\""}, so the line stays valid.
  • Framework attributes are removed. Attributes that cannot be written in JSX, such as @click, :class or x-on:click from Vue and Alpine, are left out of the output, and a note under the result lists each one so you can port it by hand.
  • Empty attributes. Only true boolean attributes (disabled, checked, selected, readonly, required, multiple, hidden, open and similar) are written bare. An empty value on any other attribute, such as alt="", stays alt="". CSS custom properties in a style, such as --accent: 1, keep their name as a quoted key: style={{ '--accent': '1' }}.
  • Style and script contents. The contents of a <style> block get their braces quoted one by one. Move such CSS into a stylesheet or a template literal.

Pasting a full document is fine: only the body content is converted, so the <html> and <body> tags are dropped, and anything the browser files under <head> (title, meta, link) is not converted. Ticking "Wrap in a component" puts the result inside export default function Component() with a fragment (<>...</>) around it, so sibling elements are allowed. Stray markup you do not want to carry over can be trimmed first with the HTML minifier.

Examples

  • ▸ Turning a label with a for attribute and an input with a class into htmlFor and className JSX.
  • ▸ Converting style="margin-top: 4px" into style={{ marginTop: '4px' }}.

Frequently asked questions

What does the converter change?

It renames attributes to their JSX form (class to className, for to htmlFor, tabindex to tabIndex, xlink:href to xlinkHref), converts inline style strings to style objects, turns inline event handlers into function stubs, self-closes void tags, escapes { } < > in text and turns HTML comments into JSX comments. Attributes JSX cannot hold, such as Vue and Alpine @click or :class, are left out and listed in a note under the result.

Will it handle a whole page?

It converts any markup fragment you paste. For a full document only the body content is converted, so title, meta and link tags in the head are not. Tick Wrap in a component to put the result inside an export default function with a fragment around it.

Does it convert onclick handlers into working React code?

It converts onclick="go()" into onClick={() => { ... }}, a function with the original code on its own lines inside, because React cannot use a string as a handler. Review the stub: inline handlers used this and event, which behave differently in React. Move the logic into a real handler such as onClick={go} when you can.

Are the spaces between my inline tags kept?

They do not. A space between text and an inline element, as in Hello <b>world</b> again, is written as {' '} so it still renders. Whitespace between block elements, such as line breaks and indentation, is dropped, as it is in HTML.

How does it convert inline styles?

A style string such as "background-color: red; margin-top:4px" becomes the object style={{ backgroundColor: 'red', marginTop: '4px' }}, with property names camelCased and values kept as strings. CSS custom properties keep their name as a quoted key, so --accent: 1 becomes { '--accent': '1' }.