SVG to JSX Converter
Turn SVG markup into a React or React Native component — attributes mapped, nothing uploaded.
A name that is not a legal identifier is turned into PascalCase, and an empty one becomes SvgComponent; the note under the panes says which name was emitted. Spread props puts {...props} on the root element, so the caller can pass className, width, aria-label and event handlers straight through. currentColor rewrites literal fill and stroke colors so the icon takes the surrounding text color; none, transparent, inherit and url(#…) references are left alone. Size prop replaces the root width and height with one size prop, defaulting to the width the file already declares; with no usable width or height it is skipped rather than invented.
Sanitized source SVG
The preview renders the sanitized input SVG in an image, not the JSX above — scripts, event handlers and external references are removed before it is drawn, and it is never inserted into this page as markup.
Press ? for shortcuts · Stored only on this device —
Do it offline:$ npx @svgr/cli --typescript icon.svg
How to use it
- Paste or drop your SVG — an unedited Figma or Inkscape export is fine.
- Name the component and pick TypeScript or JavaScript, React or React Native.
- Turn on the props spread, the currentColor swap or a size prop as the component needs.
- Read the chips under the output, then copy the component or save it as a file.
Paste SVG markup and take away a component you can import: attribute names translated to the props React accepts, a style attribute rebuilt as an object, a named function with a default export. Switch the target and the element names become react-native-svg components. It all runs in this tab, and the markup is never uploaded.
How it works
JSX props are not SVG attributes: a hyphenated name is no legal JavaScript property. Each one is looked up in a table built from the MDN SVG attribute reference and React's property list — stroke-width becomes strokeWidth, class becomes className, xlink:href becomes xlinkHref, while viewBox and preserveAspectRatio keep the spelling React expects. A name the table lacks is never guessed at; it rides along unchanged, chipped.
A style attribute is a string and React wants an object, so each declaration splits at its first colon: fill:#0ea5e9;stroke-width:0.5 arrives as { fill: "#0ea5e9", strokeWidth: "0.5" }. Values stay quoted, because a bare 0.5 reads as user units. For React Native a second table swaps element names — svg becomes Svg, path becomes Path, tspan becomes TSpan — and writes the import line; that output needs the react-native-svg package installed.
Examples
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#111827" stroke-width="2" stroke-linecap="round"><path d="M4 12h16"/><path d="M12 4v16"/></svg>import type { SVGProps } from "react";
export function SvgComponent(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#111827" strokeWidth="2" strokeLinecap="round" {...props}>
<path d="M4 12h16" />
<path d="M12 4v16" />
</svg>
);
}
export default SvgComponent;
The defaults: both hyphenated attributes take camelCase prop spellings, the spread lands last so a caller can override them, and path data is copied character for character.
<svg viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path fill="#e11d48" d="M8 1.6l1.9 4.3 4.6.4-3.5 3 1 4.6L8 11.5l-4 2.4 1-4.6-3.5-3 4.6-.4z"/></svg>import type { SVGProps } from "react";
export function StarIcon({ size = 16, ...props }: SVGProps<SVGSVGElement> & { size?: number | string }) {
return (
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" width={size} height={size} {...props}>
<path fill="currentColor" d="M8 1.6l1.9 4.3 4.6.4-3.5 3 1 4.6L8 11.5l-4 2.4 1-4.6-3.5-3 4.6-.4z" />
</svg>
);
}
export default StarIcon;
Icon settings: the hardcoded pink becomes currentColor, and the root size collapses into one size prop defaulting to the original width.
<svg viewBox="0 0 10 10" width="10" height="10" xmlns="http://www.w3.org/2000/svg"><rect class="tile" style="fill:#0ea5e9;stroke-width:0.5;font-family:Inter, sans-serif" x="1" y="1" width="8" height="8" rx="1"/></svg>import type { SVGProps } from "react";
export function SvgComponent(props: SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 10 10" width="10" height="10" xmlns="http://www.w3.org/2000/svg" {...props}>
<rect className="tile" style={{ fill: "#0ea5e9", strokeWidth: "0.5", fontFamily: "Inter, sans-serif" }} x="1" y="1" width="8" height="8" rx="1" />
</svg>
);
}
export default SvgComponent;
class arrives as className, and three declarations become one object literal — the font stack keeping its comma and spaces.
<svg viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#0f172a" stroke-width="2"><circle cx="12" cy="12" r="9"/><path stroke-linecap="round" d="M8 12l3 3 5-6"/></g></svg>import Svg, { Circle, G, Path } from "react-native-svg";
export function CheckBadge(props) {
return (
<Svg viewBox="0 0 24 24" width="24" height="24" {...props}>
<G fill="none" stroke="#0f172a" strokeWidth="2">
<Circle cx="12" cy="12" r="9" />
<Path strokeLinecap="round" d="M8 12l3 3 5-6" />
</G>
</Svg>
);
}
export default CheckBadge;
React Native, in JavaScript: three elements take capitalised names, the import lists exactly those, and xmlns is reported as left out.
<?xml version="1.0" encoding="UTF-8"?>
<!-- Drawn in Inkscape -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.1.2" width="8px" height="8px" viewBox="0 0 8 8"><path inkscape:label="frame" d="M0 0h8v8H0z"/></svg>import type { SVGProps } from "react";
/* Drawn in Inkscape */
export function SvgComponent(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" {...props}>
<path d="M0 0h8v8H0z" />
</svg>
);
}
export default SvgComponent;
An untouched editor export: the declaration goes, the comment survives, the inkscape: attributes are named as dropped and 8px becomes 8 — six chips.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Editor prefixes cannot survive as props
inkscape:label, sodipodi:nodetypes and xmlns:xlink are legal XML, and no React prop name may hold a colon. Each is left out and named with its line, so you see what your editor added.
An unknown attribute is passed along, not deleted
Figma's mask-type="alpha" is not in the mapping table. Rather than drop it or invent a camelCase twin, the emitter copies it verbatim and chips it — the keep-or-delete call is yours.
px comes off the root size, a percentage does not
width="8px" becomes width="8" on the root, because JSX size props are user units, and a chip names the original. width="100%" passes through: dropping the sign would shrink a stretch-to-fit drawing.
class and className in one tag: the first wins
Hand-edited files sometimes carry both. They map to one prop, which JSX cannot hold twice, so the earlier wins and the later is chipped as discarded.
The preview is a neutralised twin of your paste
The thumbnail draws a stripped copy through an image element, and whatever was removed to make it safe is listed beside it. Your component is computed from the original bytes, so the two differ.
Common errors
Input is larger than the 1 MB limit for this tool. For bigger files use the CLI: npx @svgr/cli --typescript icon.svg- Past one megabyte the paste is declined whole rather than cut short — half a component is worse than none. That much SVG is usually a traced photograph; the printed command reads the file from disk.
The root element is <html>, not <svg> — this tool needs SVG markup.- A copied page fragment brings the wrong root, so nothing is emitted. Copy the <svg> element and its children, or open the .svg file itself.
Unquoted value for attribute 'viewBox' at line 1, column 14 — probable cause: attribute values must be wrapped in single or double quotes.- SVG is XML, so a bare value is refused with its line and column rather than repaired behind your back. Quote what the message names.
mask-type on <mask> at line 1 is not in the JSX attribute table — it is passed through unchanged, so React renders it but TypeScript will not know it.- A chip, not a failure: the attribute is in your component. Keep or delete it as your renderer requires — the table grows by review, never guesswork.
<foreignObject> at line 1 has no react-native-svg component — the element and its contents were left out.- react-native-svg implements a subset of SVG, and foreignObject is outside it. The rest of the drawing is still emitted, and the chip names what is missing.
FAQ
Why did fill-opacity become fillOpacity but data-icon stay as it is?
Because React treats them differently. Presentation attributes go through DOM properties, which carry camelCase names, while data- and aria- attributes reach the element exactly as given. The table follows that documented pair of rules.
Does the React Native output work without extra packages?
No. Svg, Path, G and the rest come from react-native-svg, which you install yourself; the import line names it. Elements it has no component for, foreignObject among them, are reported and left out.
What happens to a script or an onclick in my file?
They are listed and never written into the component. Each warning carries its position, and the emitted JSX holds markup only.
Is this the same as running SVGR?
It answers the same first question — attribute and element naming — in your browser, with every dropped construct on screen. It is not a reimplementation: sprite sheets, templates, ref forwarding, an optimizer step and other framework targets sit outside what this page does.
Does my markup leave this browser?
No. The conversion is a function running in this tab, dropped files are read locally, and there is nowhere to send the markup. No network request carries anything you paste.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Convert the markup in the pane again with the options as they stand.
- ⌘/Ctrl + S
- Save the component as a dated svg-to-jsx file, ahead of the browser's save-page action.
- ⌘/Ctrl + C
- Copies the component when nothing is selected; a selection copies as usual.
- ⌘/Ctrl + Shift + C
- Copy the component whatever the selection.
- t
- Switch theme, unless a text field or an IME composition has focus.
- ? or ⌘/Ctrl + /
- Open the help dialog listing these bindings.
- Esc
- Close the help dialog.