Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

HTML to JSX Converter

Paste HTML or SVG, get React JSX that renders exactly the same.

Developer No upload Works offline Free, no sign-up

A snippet, a whole page or an SVG file (you can also drop a file here). Nothing you paste leaves this browser.

Output

Next steps

About the HTML to JSX Converter

Paste HTML or an SVG file and get JSX you can drop into a React component: class becomes className, for becomes htmlFor, style="…" becomes a style object, stroke-width becomes strokeWidth, void elements are self-closed and comments become {/* … */}. Attribute names come from React DOM's own table of HTML and SVG attributes, so tabindex, viewbox or xlink:href get the exact spelling React expects.

The converter also keeps what the page looks like. JSX throws away whitespace that contains a line break, so a space HTML shows between two links on separate lines is written {' '}, and <pre> text becomes string literals so its spaces survive. The markup is laid out the way Prettier formats JSX (the code of event handlers is kept as written). Everything runs in your browser.

How to use it

  1. Paste HTML or SVG into the box, open an .html or .svg file, or press Load sample. The JSX updates as you type.
  2. Choose JSX or TSX, and tick Wrap in a component to get export default function Name() { return (…); }. For icons, tick Spread props so <Icon className="…" /> reaches the root <svg>.
  3. Read the notes under the result: they list everything that was renamed or left out, with the line of your input, and say what to change by hand (for example this in an inline handler).
  4. Copy the code or download it as a .jsx or .tsx file.

Examples

Form markup
Input
<label for="email">Email</label>
<input type="email" id="email" value="[email protected]" required tabindex="1">
Result
<>
  <label htmlFor="email">Email</label>{' '}
  <input type="email" id="email" defaultValue="[email protected]" required tabIndex={1} />
</>

value is the field's starting value in HTML, which React calls defaultValue (a value without onChange would make the field read-only). The {' '} keeps the space HTML shows between the label and the field.

Inline styles and handlers
Input
<button style="font-size: 14px; z-index: 2" onclick="save()">Save</button>
Result
<button style={{ fontSize: '14px', zIndex: 2 }} onClick={() => save()}>
  Save
</button>

Properties are camelCased; z-index takes a plain number because React adds no px to it, while 14px stays a string.

An SVG icon as a component
Input
<svg viewBox="0 0 24 24" stroke-width="2" class="icon"><path d="M5 12h14"/></svg>
Result
export default function Icon(props) {
  return (
    <svg viewBox="0 0 24 24" strokeWidth="2" className="icon" {...props}>
      <path d="M5 12h14" />
    </svg>
  );
}

With Wrap in a component, component name Icon and Spread props. In TSX the props are typed ComponentProps<'svg'>.

Common uses

  • Moving a landing page, email template or Bootstrap/Tailwind snippet into a React, Next.js or Remix component.
  • Turning SVG icons exported from Figma, Illustrator or Inkscape into icon components (editor metadata is removed).
  • Converting markup from a CMS or a design handoff without fixing every attribute by hand.

What changes

  • Attributes: renamed with React DOM's attribute table — class → className, for → htmlFor, tabindex → tabIndex, readonly → readOnly, charset → charSet, SVG fill-rule → fillRule, xlink:href → xlinkHref, xml:space → xmlSpace. data-* and aria-* stay as they are.
  • Booleans: disabled="disabled" becomes disabled; an attribute without a value that is not a boolean, such as data-open, becomes data-open="" (in JSX a bare name means true).
  • Numbers: tabindex, colspan, rowspan, maxlength and similar integer attributes become {2}.
  • Styles: style becomes an object with camelCased names (-webkit- → Webkit, -ms- → ms, custom properties stay quoted as '--brand'). Numbers keep their units as strings, except 0 and the unitless properties React lists (zIndex, opacity, lineHeight, flexGrow …). !important cannot be expressed and is reported.
  • Event handlers: onclick="save()" becomes onClick={() => save()} and statements get a block. A leftover javascript: at the start is removed (in handler code it is only a label), a comma expression is put in parentheses so nothing after the comma runs while React renders, and a // comment gets its own line end so it cannot hide the closing braces. React has no props for window events such as onbeforeunload; those are reported.
  • Form fields: value → defaultValue, checked → defaultChecked, <option selected> → defaultValue on the <select>, and <textarea> content → defaultValue. Inputs whose value is fixed (checkbox, radio, submit, hidden) keep value.
  • Structure: end tags HTML lets you leave out (</li>, </p>, </td>) are added where the browser would close them, <tbody> is added around table rows, <br> becomes <br />, and <div/> is read as an empty element.
  • Style and script elements: the content of <style> and <script> goes into dangerouslySetInnerHTML, because React escapes text inside them when it renders on the server (a > b would become a &gt; b).

Why {' '} appears

HTML turns any run of spaces and line breaks into one space, so <a>Home</a>⏎<a>About</a> shows "Home About". JSX instead removes whitespace that contains a line break, so the same lines in JSX show "HomeAbout". The converter writes {' '} wherever HTML would have shown such a space between inline content, and drops whitespace that never shows (between block elements such as <div> and <p>, around <br>, at the start and end of a block). Spaces on the same line need nothing: JSX keeps them.

If your CSS turns those elements into blocks or flex items, the spaces do not matter: untick Keep spaces between inline elements to drop them, as most converters do.

Checked against a real JSX compiler

The test suite compiles the output with sucrase (which follows Babel's JSX whitespace rules) and compares the text React would render with the text a browser shows for the original HTML — for hand-written cases and for 300 generated snippets of mixed inline and block markup, at two line widths. Every output is also parsed with TypeScript's JSX parser.

Limitations

  • Inline event handler code is otherwise copied as written, so a mistake in it stays (the notes flag unbalanced brackets and unclosed quotes where they can tell). Code that relies on this (the element in HTML) or return false (cancelling the event in HTML) needs event.currentTarget and event.preventDefault() in React; the notes point these out.
  • React never runs <script> content it renders; scripts are kept so you can see them, but should move into effects or modules.
  • Template syntax from Vue, Alpine or Angular (@click, :class, *ngIf) is not HTML and is left out — rewrite it with React state.
  • Elements and attributes with namespaces that JSX cannot express (inkscape:label, Word's <o:p>) are removed.
  • Whitespace is judged by the default display of each element; CSS that changes display or white-space can make a kept or dropped space matter differently.

Privacy

Everything happens in your browser. What you enter or open here is not uploaded or stored by MySmartCoPilot.

Frequently asked questions

Why did value become defaultValue?

In HTML, value on an input is only the starting text. In React, value makes the field controlled: without an onChange handler it can no longer be edited. defaultValue (and defaultChecked for checkboxes) keeps the HTML behaviour. If you want a controlled field, rename it back to value and add state and an onChange handler.

Do I need to import React?

Not with the JSX transform used since React 17 (Next.js, Vite, Create React App 4+ and current Babel and TypeScript setups). With an older setup that uses React.createElement, add import React from 'react'; at the top. The TSX output with spread props imports the ComponentProps type only.

Is my HTML uploaded?

No. The conversion runs entirely in your browser and works offline once the page has loaded; nothing you paste or open is sent anywhere.

Why are some character references turned into characters?

JSX understands only the 253 HTML 4 references such as &copy; or &nbsp;; newer HTML5 names such as &check; would show up literally. The converter writes such characters directly (✓), and keeps invisible ones (non-breaking and zero-width spaces, soft hyphens) as references so you can see them. &lt;, &gt; and braces are escaped so the JSX still parses.

Can it convert a whole web page?

Yes. With Only the <body> content ticked (the default) a full page becomes the JSX of its body, and the notes list what was in <head>. Untick it to convert <html>, <head> and <body> too, for example for a root layout.

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.