Skip to content
SVGtoReact

SVG to React Component: A Practical Guide

Turn an SVG to React component code by hand or with SVGR. Rename attributes, keep viewBox, spread props, make IDs unique, and label icons for screen readers.

By SVG to React. Published . Updated .

There are three common ways to show an SVG in React. You can load the file with an img tag, paste the markup inline in JSX, or wrap that markup in a component. This post is about the third option: what to change, what to keep, and which details make the difference between an icon that works in one place and a component you can use across an app.

When a component is the right choice

An img tag is the simplest choice. The file is cached by the browser, it is not part of your JavaScript bundle, and its markup cannot conflict with the page. The cost is control. CSS on the page cannot reach inside the image, so you cannot change its color on hover. You cannot pass props to it, and you cannot animate one path inside it.

A component gives you that control. The SVG elements become part of the DOM, so they inherit color, respond to CSS, and accept props such as className, width, and aria-label. Icons, logos that switch with the theme, and small interface graphics are good candidates. A large illustration that never changes is usually better as a file.

Step 1: Start from a clean SVG

Design tools export more than the drawing. A typical export looks like this:

arrow-right.svg (editor export)
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generator: an editor export -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24"
  viewBox="0 0 24 24" fill="none" stroke="#111827" stroke-width="2"
  stroke-linecap="round" stroke-linejoin="round">
  <path d="M5 12h14" />
  <path d="m12 5 7 7-7 7" />
</svg>

The XML declaration and the comment are not valid in JSX and carry no rendering information, so remove them. Editor exports can also include metadata elements, editor namespaces such as sodipodi or inkscape, and empty groups. You can delete those by hand or run the file through an optimizer. The SVG optimizer on this site runs SVGO's preset-default in the browser and keeps the viewBox.

Keep the viewBox. It defines the coordinate system of the drawing. Width and height only set the size of the box that the drawing is scaled into. If you remove the viewBox, the component can no longer scale cleanly when you change its size.

Step 2: Rename attributes for JSX

JSX is JavaScript, and React expects DOM property names rather than XML attribute names. Three rules cover most icons:

viewBox keeps its name, and so do aria-* and data-* attributes. The full list, including xlink:href and xml:space, is in SVG to JSX: Attribute Rules Explained.

Step 3: Wrap it in a function and spread props

A component is a function that returns the SVG. The detail that matters most is where you place {...props}. Put it last on the root svg element, so a caller can override any default you set:

ArrowRightIcon.jsx
export function ArrowRightIcon(props) {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width={24}
      height={24}
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={2}
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      {...props}
    >
      <path d="M5 12h14" />
      <path d="m12 5 7 7-7 7" />
    </svg>
  );
}

The hard-coded stroke color is now currentColor. That keyword means the value of the CSS color property, so the icon takes the text color of the element around it. For an outline icon, keep fill="none" and set the stroke to currentColor. For a solid icon, set the fill instead. Do not apply currentColor to multi-color artwork without checking it, because every color becomes the same one.

Step 4: Decide how the icon is sized

The width and height defaults are only a starting size. Callers override them through props or CSS:

Usage
<button className="text-teal-700">
  Next <ArrowRightIcon width="1em" height="1em" />
</button>

<ArrowRightIcon className="h-6 w-6 text-slate-500" />

A size of 1em makes the icon follow the font size of its parent, which is handy for icons next to text. SVGR's icon option sets width and height to 1em for you. If you size icons only with CSS classes, you can drop the width and height attributes. In that case, set a size in CSS everywhere the icon appears, because an svg without a size takes a default size that depends on its container.

Step 5: Make it accessible

Every icon falls into one of two cases.

Decorative icons sit next to text that already says what they mean, such as an arrow inside a “Next” button. Hide them from assistive technology with aria-hidden="true", as in the component above. Otherwise a screen reader may announce an unlabeled image.

Meaningful icons carry information on their own, such as a status dot with no text. Give them role="img" and a name. Use either aria-label, or a title element that aria-labelledby points to:

StatusIcon.jsx
import { useId } from "react";

export function StatusIcon({ label, ...props }) {
  const titleId = useId();
  return (
    <svg viewBox="0 0 24 24" role="img" aria-labelledby={titleId} {...props}>
      <title id={titleId}>{label}</title>
      <circle cx="12" cy="12" r="10" fill="currentColor" />
    </svg>
  );
}

// <StatusIcon label="Build passed" className="text-green-600" />

If an icon is the only content of a button, label the button instead, for example with aria-label="Close", and keep the icon decorative.

Step 6: Give each instance unique IDs

This is the bug that shows up last. An id inside an inline SVG belongs to the whole document. If the icon defines a gradient with id="g" and you render it twice, both instances refer to the first gradient in the page. If that first copy is hidden or removed, the second icon can lose its fill. Editors and optimizers often shorten IDs to single letters, which makes collisions between different icons likely as well.

React's useId hook returns an ID that is unique for each component instance and stable between server and client rendering:

BadgeIcon.jsx
import { useId } from "react";

export function BadgeIcon(props) {
  const gradientId = useId();
  return (
    <svg viewBox="0 0 24 24" aria-hidden="true" {...props}>
      <defs>
        <linearGradient id={gradientId} x1="0" x2="1">
          <stop offset="0" stopColor="#0f766e" />
          <stop offset="1" stopColor="#38bdf8" />
        </linearGradient>
      </defs>
      <circle cx="12" cy="12" r="10" fill={`url(#${gradientId})`} />
    </svg>
  );
}

The same applies to clipPath, mask, filter, pattern, and marker IDs. Search the SVG for id= and url(# before you publish a component.

Automating the conversion

Doing these steps by hand is fine for a few icons. For more than a few, use SVGR, the library most converters are built on. It parses the SVG, renames attributes, converts styles, and prints a component. The SVG to React converter on this site runs SVGR in your browser, so you can paste one file and copy the result. For a whole folder, the batch tool builds a zip, and SVGR's command-line tool writes the files directly:

Terminal
# One-off: every .svg in assets/icons becomes a component in src/icons
npx @svgr/cli --out-dir src/icons --typescript --icon -- assets/icons

You can also convert at build time. In a Vite project, vite-plugin-svgr turns an import that ends in ?react into a component:

vite.config.js
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import svgr from "vite-plugin-svgr";

export default defineConfig({ plugins: [react(), svgr()] });

// In a component file
import Logo from "./logo.svg?react";

A build-time transform keeps the original .svg files in your repository. Committed components are plain code that reviewers can read. Both approaches work. The trade-offs are covered in SVGR vs online converter.

Checklist before you ship

If you write TypeScript, the next step is typing the props. See SVG to TSX: Typed SVG Icons in React, or use the SVG to TSX converter to get the typed version directly.

Questions

Should every SVG in a React app become a component?

No. A component pays off when the graphic needs props, CSS color inheritance, animation, or accessible text that changes. A large illustration that never changes can stay a file loaded with an img tag, which the browser caches separately from your JavaScript.

Why does my icon stop scaling after I edit it?

The usual cause is a missing viewBox. width and height only set the box the drawing is placed in. viewBox defines the coordinate system that is scaled into that box. Without it, changing width or height crops or pads the drawing instead of resizing it.

Do I need useId for every SVG component?

Only for SVGs that define an id and refer to it, such as a gradient used with fill="url(#id)", a clipPath, a mask, or a title referenced by aria-labelledby. IDs are global to the document, so two copies of the same component would otherwise share one id.

Is the converter output the same as running SVGR myself?

The JSX and TSX tools on this site run @svgr/core with the JSX plugin in your browser, so the attribute rewriting is SVGR's own. Your local result can still differ if your SVGR config turns on SVGO, Prettier, icon mode, or a template that this site does not use.