HTML to JSX Converter Online

Paste HTML or an SVG icon and get JSX you can put straight into a React component. It is for anyone moving markup from a template, a design tool, a CMS or an old page into React: class becomes className, for becomes htmlFor, style becomes an object, attributes get React's camelCase names, every tag is closed and braces in text are escaped. You get a snippet or a whole component, in JSX or TSX.

Everything runs in your browser, so your markup is not uploaded, and it is never rendered or run. DevPrix reads the HTML the way browsers do and lists what it removed or changed, with the line and column: event handlers such as onclick, inline scripts, Inkscape or Illustrator metadata, and anything React would warn about.

HTML or SVGPaste, type or drop an .html or .svg file
JSXsnippet.jsx
/* Newsletter signup card */
<div
  className="card signup"
  style={{ "--accent": "#4f46e5", maxWidth: "420px", padding: "24px" }}
>
  <h2 className="card-title">Get the release notes</h2>
  <p>
    One email per release &mdash; no tracking, and you can leave any&nbsp;time.
  </p>
  <form
    action="/subscribe"
    method="post"
    /* onsubmit="return validate(this)" */
  >
    <label htmlFor="email">Email</label>{" "}
    <input
      id="email"
      name="email"
      type="email"
      defaultValue="ada@example.com"
      autoComplete="email"
      required
    />{" "}
    <label className="check">
      <input type="checkbox" name="digest" defaultChecked /> Weekly digest
    </label>{" "}
    <select name="format" defaultValue="text">
      <option value="html">HTML</option>
      <option value="text">Plain text</option>
    </select>{" "}
    <textarea
      name="note"
      rows={3}
      maxLength={280}
      placeholder="Anything else?"
    />{" "}
    <button type="submit" className="btn btn-primary" tabIndex={0}>
      Subscribe
    </button>
  </form>
</div>
Warnings (1)What was removed or changed, and what to check. Click one to find it in the input.
Converted with 1 warning13 elements12 attributes changed891 B → 1.1 KBProcessed locally
Send output toText Diff Checker

How to use it

  1. Paste HTML or SVG into the input, or open or drop an .html or .svg file; it is converted as you type.
  2. Choose Snippet or Component, JSX or TSX, and the options, then read the Warnings list for what was removed or needs a look.
  3. Copy or download the result (⌘⇧C copies it), or send it to another tool.

Related tools

HTML Formatter & MinifierFormatHTML Entity EncoderWeb & APICSS Formatter & MinifierFormatText Diff CheckerFormat

Next: tidy the source markup first in the HTML Formatter, look up an entity in the HTML Entity Encoder, or compare two versions of a component in the Text Diff Checker.

What this tool does

The converter parses your input with the same rules browsers use, so it converts the tree a browser would build: a <p> that a <div> closes, an optional </li>, a <tbody> added to a table, an SVG tag such as linearGradient. It then writes JSX for React 19. Attribute names come from React's own list of DOM properties, so tabindex becomes tabIndex, readonly becomes readOnly, http-equiv becomes httpEquiv and stroke-width becomes strokeWidth; aria-* and data-* attributes, and the attributes of custom elements such as <my-widget>, stay as written. A style attribute becomes an object with camelCase keys (-webkit- becomes Webkit, -ms- becomes ms, and --custom properties stay quoted), a boolean attribute such as disabled becomes a bare prop, and numeric props such as tabIndex, colSpan and maxLength become numbers. By default, form values become defaultValue and defaultChecked, and <option selected> becomes a defaultValue on its <select>, because value without an onChange handler makes React render a read-only field. Text is escaped where JSX needs it: { and } become {"{"} and {"}"}, < and > become &lt; and &gt;. JSX joins lines and drops line breaks between tags, so where a space between inline content would be lost at a line break, the tool writes {" "}; spaces next to blocks, which browsers do not show, are left out. Text in <pre> and <textarea> is kept exactly, in a template literal. Comments become {/* … */}, an inline <script> becomes dangerouslySetInnerHTML, and for a full page only the <body> content is converted. Event handlers cannot be turned into code safely, so they are removed, kept as a comment in the tag, and listed with the React event to use instead.

Example input and output

A form, with uncontrolled fields
Input
<form class="search" action="/search">
  <label for="q">Search</label>
  <input id="q" name="q" type="search" value="react" autofocus>
  <label><input type="checkbox" name="exact" checked> Exact match</label>
  <select name="sort">
    <option value="new">Newest</option>
    <option value="top" selected>Top</option>
  </select>
  <button type="submit" tabindex="0" onclick="track()">Go</button>
</form>
Output
<form className="search" action="/search">
  <label htmlFor="q">Search</label>{" "}
  <input id="q" name="q" type="search" defaultValue="react" autoFocus />{" "}
  <label>
    <input type="checkbox" name="exact" defaultChecked /> Exact match
  </label>{" "}
  <select name="sort" defaultValue="top">
    <option value="new">Newest</option>
    <option value="top">Top</option>
  </select>{" "}
  <button type="submit" tabIndex={0} /* onclick="track()" */>Go</button>
</form>

The line breaks between the fields are spaces on the page, so they become {" "}. The onclick handler is listed as removed: write onClick={track} in your component.

An SVG icon as a component, without xmlns
Input
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
  <circle cx="12" cy="12" r="10"/>
  <path d="m9 12 2 2 4-4"/>
</svg>
Output
export default function CheckIcon() {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      className="icon"
    >
      <circle cx="12" cy="12" r="10" />
      <path d="m9 12 2 2 4-4" />
    </svg>
  );
}

With Component, the name CheckIcon and Drop SVG xmlns. viewBox keeps its case, and the presentation attributes are camelCased.

Braces, entities and preformatted text
Input
<p class="hint">Templates use {name} and {{ count }} &mdash; see the <a href="/docs/templates/placeholders">placeholder guide</a> for the full list.</p>
<pre>npm run build
npm start</pre>
Output
<>
  <p className="hint">
    Templates use {"{"}name{"}"} and {"{{"} count {"}}"} &mdash; see the{" "}
    <a href="/docs/templates/placeholders">placeholder guide</a> for the full
    list.
  </p>
  <pre>{`npm run build
npm start`}</pre>
</>

Two root elements are wrapped in a fragment. &mdash; stays, because JSX reads it the same way; turn on Decode entities to get the character instead.

Common errors and fixes

Invalid DOM property `class`. Did you mean `className`?
class and for are reserved words in JavaScript, and React uses the DOM property names className and htmlFor. React still renders class, but warns. The converter renames both, along with the other attributes React spells differently, such as tabindex and stroke-width.
The `style` prop expects a mapping from style properties to values, not a string.
In JSX, style takes an object: style="margin-top: 4px" becomes style={{ marginTop: "4px" }}. React throws on a string, so the page does not render at all. The converter splits declarations correctly even when a value contains a semicolon, as in url("a;b.png").
You provided a `value` prop to a form field without an `onChange` handler.
In React, value makes a field controlled: React keeps it at that value, so typing does nothing. For a starting value the user can change, use defaultValue (and defaultChecked for checkboxes), which is the default here. If you need a controlled field, turn off Uncontrolled form fields and add state with an onChange handler.
JSX element 'br' has no corresponding closing tag.
HTML lets <br>, <img> and <input> stand alone, but JSX needs every element closed. The converter writes <br />, <img />, <input /> and closes empty elements such as <div /> too.
Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
TypeScript rejects } and > in JSX text, and { starts an expression. The converter writes braces as {"{"} and {"}"}, and < and > as &lt; and &gt;, so code samples and template syntax such as {{ name }} show as written.
JSX expressions must have one parent element.
A JSX expression returns one element. When you paste several siblings, the converter wraps them in a fragment, <>…</>, which adds nothing to the page.
Expected `onClick` listener to be a function, instead got a value of `string` type.
HTML handlers are strings of code; React handlers are functions. The converter removes onclick="…", keeps it as a comment in the tag, and names the React prop to use, such as onClick={handleClick}.

Common use cases

  • Turning a static HTML template or a landing page section into a React component
  • Pasting an SVG icon from Figma, Illustrator, Inkscape or an icon set into JSX
  • Moving Bootstrap or Tailwind examples into a React or Next.js project
  • Converting markup from a CMS, an email builder or DevTools into JSX
  • Checking what React would warn about in a piece of HTML before you paste it

Privacy and security notes

  • Your markup is parsed and converted by JavaScript in this browser tab. It is not sent to DevPrix or stored on a server.
  • The input is never rendered or run: the JSX is shown as highlighted text, so scripts, event handlers, iframes and images in it do nothing, and nothing they refer to is fetched.
  • Share copies a link that carries your input after the # in the URL. Browsers do not send that part to servers, but anyone you give the link to can read it.

Frequently asked questions

Why is there {" "} in the output?

JSX handles whitespace differently from HTML. It trims each line and drops line breaks between tags, so a space that sat at the end of a line next to a tag would be lost. In HTML that space is visible: it separates "Read the" from a link, or two form fields. Where a line break would lose a space, the converter writes {" "}. Spaces next to block elements, such as between two <div>s, are not shown by browsers, so they are left out.

What happens to onclick and other event handlers?

They are removed, because an HTML handler is a string of code that runs in the page's global scope, with its own this and event, and it cannot be turned into a React function safely. With Keep comments on, the original stays as a comment in the tag, such as /* onclick="save()" */, and the Warnings list names the React prop to use, such as onClick={…}. Move the code into a function in your component and pass it to that prop.

Should I use defaultValue or value?

defaultValue (and defaultChecked) sets the starting value and lets the user change it: React calls this an uncontrolled field. value (and checked) makes the field controlled: React keeps it at that value until you update it from an onChange handler, and warns when there is none. The converter uses defaultValue by default, and moves <option selected> to the <select>, as React asks. Turn off Uncontrolled form fields to get value and checked, then add state.

Does it work with SVG icons from Figma or Illustrator?

Yes. Presentation attributes such as stroke-width and fill-rule become strokeWidth and fillRule, xlink:href becomes xlinkHref, viewBox and preserveAspectRatio keep their case, and tags such as linearGradient and clipPath are restored even when the file wrote them in lowercase. The <?xml?> declaration is dropped, and so are the inkscape:, sodipodi: and rdf: elements and attributes that drawing apps add, since React cannot render namespaced names. Turn on Drop SVG xmlns for inline icons: inside an HTML page the namespace is not needed.

What is the difference between JSX and TSX output?

The markup is the same, and numeric props such as tabIndex are always numbers, as React's TypeScript types expect. In TSX, a style object that sets a CSS variable such as --accent is cast with as CSSProperties, because TypeScript does not know custom properties, and a component gets the matching import type line. The Warnings list also notes elements React's types do not include, such as <font> or a custom element, which need a type declaration.

Why did my <script> become dangerouslySetInnerHTML?

In JSX the text inside an element is escaped, which would break a script's quotes and < signs, so the code goes into dangerouslySetInnerHTML as a template literal. React does not run a script it adds while rendering in the browser, only one that arrives in server-rendered HTML, so the tool flags inline scripts. Load the code with an import, in useEffect, or with your framework's script component, such as next/script. JSON-LD blocks are not flagged: they are data and work as they are.

Guides