tool / svg to jsxruns in your browser
SVG to JSX converter
Paste an SVG and get a React component: every attribute in the name React wants, the style as an object, comments and anything that could run left out, typed props if you write TypeScript. Wrap it in <ScrollDraw> and it draws itself as the page scrolls.
- Names renamed
- 138
- Rules
- 16
- Output
- TSX · JSX
- Uploads
- 0
Convert SVG to a React component
The sample is a cup of coffee, exported the way a drawing tool writes a file. Change any option and the code follows; the table under it says what changed and why. The preview is the drawing itself, rebuilt from its shapes, never the code.
What changes, and why
Names follow react-dom’s own table of SVG attributes, the one behind its “Invalid DOM property” warning, so every prop written here renders back as the attribute the SVG had. Each row below is the converter’s output for the snippet beside it.
| Rule | SVG | JSX | Why |
|---|---|---|---|
| Hyphenated names | <path stroke-width="2" fill-rule="evenodd"/> | <path strokeWidth="2" fillRule="evenodd" /> | React props are camelCase; React writes it back hyphenated |
| clip-path, stop-color… | <stop stop-color="#fff"/> | <stop stopColor="#fff" /> | React props are camelCase; React writes it back hyphenated |
| class | <g class="steam"/> | <g className="steam" /> | Class is a JavaScript keyword, so React names it className |
| style | <g style="opacity: .6; --rise: 12px"/> | <g style={{ opacity: '.6', '--rise': '12px' }} /> | React takes an object: camelCase keys, string values, custom properties quoted |
| xlink:href | <use xlink:href="#leaf"/> | <use href="#leaf" /> | SVG 2 reads plain href, and so does every browser in use |
| xml:space | <text xml:space="preserve">Hi</text> | <text>Hi</text> | Deprecated in SVG 2. If a text needs its spaces kept, set white-space in CSS |
| xmlns:xlink | <svg xmlns:xlink="http://www.w3.org/1999/xlink"> | <svg /> | A namespace declaration, not needed once xlink:href is href |
| xmlns | <svg xmlns="http://www.w3.org/2000/svg"><g xmlns="http://www.w3.org/2000/svg"/></svg> | <svg xmlns="http://www.w3.org/2000/svg"> <g /> </svg> | Only the root <svg> needs it |
| data-* and aria-* | <g data-name="Cup" aria-hidden="true"/> | <g data-name="Cup" aria-hidden="true" /> | React passes data-* and aria-* through with their hyphens |
| Case | <svg viewbox="0 0 24 24"/> | <svg viewBox="0 0 24 24" /> | SVG is case-sensitive. An HTML page fixes the case as it parses; React does not |
| Event handlers | <rect onclick="go()"/> | <rect /> | An event handler: it would run code. Add a React handler, such as onClick, yourself |
| Comments | <!-- steam --> | (removed) | JSX has no XML comments |
| Empty elements | <path d="M2 2h20"></path> | <path d="M2 2h20" /> | An element with nothing in it closes itself |
| Text | <text>a < b {c}</text> | <text>a {'<'} b {'{'}c{'}'}</text> | It would start a tag or an expression in JSX |
| <style> | <style>.a { fill: red }</style> | <style>{` .a { fill: red } `}</style> | CSS braces would start JSX expressions, so it goes in a template string |
| Editor data | <g inkscape:label="Layer 1"/> | <g /> | An editor’s own data (Inkscape, Sketch…). Browsers ignore it |
transform-origin and mask-type as attributes but its TypeScript types do not, so the converter writes them as the CSS properties of the same name in style, which do the same (a bare number in transform-origin gets the px CSS needs). And tabindex becomes tabIndex={0}, a number, because that is the one SVG prop React types as a number.Drawing it on scroll
With the wrap on, the <svg> goes inside <ScrollDraw> from svg-scroll-draw/react. It renders a <div> around the drawing and draws every path, line, polyline, polygon, rect and circle in it along its stroke as the page scrolls, from when the div’s top comes up past the bottom of the window (start: 'top bottom') to when its bottom leaves the top (end: 'bottom top'). Only the options you change are written: the easing is 'linear' and the stagger 0 unless you say otherwise.
import { ScrollDraw } from 'svg-scroll-draw/react';
import type { SVGProps } from 'react';
export function CheckDrawn(props: SVGProps<SVGSVGElement>) {
return (
<ScrollDraw easing="ease-out" stagger={0.2} once>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
width="1em"
height="1em"
{...props}
>
<circle cx="12" cy="12" r="10" />
<path d="M7 12.5l3.5 3.5 6.5-7" />
</svg>
</ScrollDraw>
);
}// app/page.tsx: a Server Component. No "use client" anywhere.
import { CheckDrawn } from './CheckDrawn';
export default function Page() {
return <CheckDrawn className="check" aria-label="Done" />;
}className and style on <ScrollDraw> go on its div, and so do data-*, aria-*, id and role; every other prop is an option. Changing one draws it afresh, and an inline object such as trigger={{ end: 'center center' }} does not count as a change each render, because options are compared by value. Left at its defaults, or with only the easing changed to ease-in, ease-out or ease-in-out, the drawing runs on the browser’s own scroll timeline where that is supported; spring, bounce, elastic, a stagger, once, another trigger or fillOpacity use the JavaScript engine. A visitor who asks for reduced motion gets the drawing whole.
fillOpacity={[0, 1]} (“fill fades in”) brings the fill up as the line draws. An <ellipse> is not in the default selector: turn it into a path with the shape to path converter.once with spring or elastic stopped on the overshoot past the end, and with bounce lost its dips, because it held the eased value. 2.23.3 holds the scroll progress and eases it after, so they settle as they should.What is left out
The paste is read by the browser’s XML parser into a document that is never put on the page, so nothing in it runs or loads while you convert. Two outputs come from it (fig. 1). The code is written from that document, and Convert: everything keeps all of it but what could run or what only an editor reads. Convert: drawing only writes the code from what the preview shows instead: the same strict rebuild the SVG path viewer uses, drawing elements and drawing attributes only.
| In the SVG | Everything | Drawing only |
|---|---|---|
| <script>, onclick and other on… attributes, javascript: links | Left out: they would run code | Left out |
| <foreignObject> | Left out: it holds HTML, not SVG | Left out |
| Editor data: inkscape:, sodipodi:, <metadata> | Left out | Left out |
| class, style, id, data-*, aria-* | Kept, converted | Left out |
| <title>, <desc>, <text> | Kept, text escaped | Left out |
| <defs>, gradients, clip paths, masks, <use>, <a> and what is in it | Kept | Left out; a gradient fill becomes currentColor |
| width and height on the <svg> | Kept, or 1em, or removed | Left out, or 1em |
| Paths, shapes and groups: geometry, transform, fill, stroke, stroke width, caps, joins, opacity | Kept, converted | Kept, converted |
| Any other attribute: stroke-dasharray, clip-path, font-size… | Kept, converted | Left out |
white-space: pre in CSS, since xml:space is removed.SVG to JSX questions
How do I convert SVG to JSX?
JSX is not HTML: an attribute becomes a JavaScript prop, so stroke-width is written strokeWidth and class is written className, a style string becomes an object, comments go, an empty element closes itself, and a { or < in text has to be escaped. Paste the SVG above and copy the result; the table under the code lists every change it made and why.
How do I turn an SVG into a React component?
Put the converted JSX in a function that returns it, and spread props onto the <svg> so whatever the caller passes (className, width, aria-label, onClick) lands on it: export function Logo(props) { return <svg … {...props}>…</svg>; }. The converter writes that function. The other ways are an <img src="/logo.svg">, which cannot be styled from your CSS or take currentColor, and a build plugin such as SVGR that imports .svg files as components.
Why does React use strokeWidth and className instead of stroke-width and class?
A JSX attribute is a JavaScript name, and class is a reserved word there, so React calls it className; React’s DOM props are camelCase, and it writes them back to the page as stroke-width and class. A hyphenated stroke-width still renders, but React warns about it in development. data-* and aria-* are the exception: they keep their hyphens.
How do I convert an SVG to TSX with typed props?
Choose TypeScript. The component then takes props: SVGProps<SVGSVGElement>, imported as a type from react: the props React gives an <svg> element, so every attribute and event handler you pass is type-checked. A style with a custom property such as --rise is cast as CSSProperties, because React’s style type does not list custom properties. The converter also says when the SVG uses a name React’s types do not know.
How do I animate an SVG React component on scroll?
Turn on the wrap: the <svg> goes inside <ScrollDraw> from svg-scroll-draw/react, which draws each path, line, polyline, polygon, rect and circle along its stroke as the page scrolls. By default it starts when the drawing’s top comes up past the bottom of the window and finishes when its bottom leaves the top; easing, stagger, once and the finish point are props. It animates strokes, so a filled icon shows at once unless you add fillOpacity={[0, 1]}, which the “fill fades in” option writes.
Does the SVG component need “use client” in Next.js?
No. The plain component has no hooks or handlers, so it is a Server Component as it is. Wrapped in ScrollDraw it still needs no directive: svg-scroll-draw/react has started with "use client" since 2.16.1, so a Server Component can render <ScrollDraw> directly. Add "use client" to your own file only if you pass ScrollDraw a function, such as onComplete, because a function cannot be sent from the server to the browser.
Is it safe to paste my SVG here?
Yes. Nothing is uploaded: the SVG is read in your browser as an inert XML document, never inserted into the page, and the code is shown only as text. The preview is rebuilt separately from drawing elements alone. The code leaves out anything that could run, <script>, event handler attributes and javascript: links, and so cannot run anything from the paste either.
Drawing on scroll with svg-scroll-draw 2.23.3: React, Next.js, or the docs.