Skip to content
SVGtoReact

SVG to JSX: Attribute Rules Explained

The SVG to JSX attribute rules in one place: className, camelCase presentation attributes, xlink and xml names, style objects, comments, and what stays as is.

By SVG to React. Published . Updated .

SVG is XML, and JSX is JavaScript that looks like XML. Most of an SVG file can be pasted into a React component unchanged, but not all of it. The parts that differ follow a small set of rules. This post lists them in the order you are likely to meet them, with the reason behind each one. That way you can fix a file by hand or check what a converter produced.

Why the names change at all

JSX props map to properties and attributes that React DOM sets on real elements. React uses the DOM property naming convention, which is camelCase, and it reserves a few words that clash with JavaScript. The SVG specification uses XML attribute names, which are often hyphenated, and some include a namespace prefix such as xlink:. React keeps a mapping between the two forms. When you write the SVG form, React renders something in many cases, but in development it logs a warning such as Invalid DOM property `stroke-width`. Did you mean `strokeWidth`?

Here is a file that exercises most of the rules, followed by the JSX that SVGR prints for it. This is real output from @svgr/core with the JSX plugin, the same library the SVG to JSX converter runs. It is reformatted here for reading.

Input SVG
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
  class="icon" width="24" height="24" viewBox="0 0 24 24"
  style="fill-opacity:.5;stroke-linecap:round">
  <!-- c -->
  <defs>
    <linearGradient id="g"><stop offset="0" stop-color="#111"/></linearGradient>
  </defs>
  <path fill-rule="evenodd" clip-rule="evenodd" stroke-width="2" d="M1 1h10"/>
  <use xlink:href="#g" xml:space="preserve" tabindex="0" data-x="1" aria-label="a"/>
</svg>
SVGR output (svg element)
<svg
  xmlns="http://www.w3.org/2000/svg"
  xmlnsXlink="http://www.w3.org/1999/xlink"
  className="icon"
  width={24}
  height={24}
  viewBox="0 0 24 24"
  style={{ fillOpacity: 0.5, strokeLinecap: "round" }}
  {...props}
>
  <defs>
    <linearGradient id="g">
      <stop offset={0} stopColor="#111" />
    </linearGradient>
  </defs>
  <path fillRule="evenodd" clipRule="evenodd" strokeWidth={2} d="M1 1h10" />
  <use xlinkHref="#g" xmlSpace="preserve" tabIndex={0} data-x={1} aria-label="a" />
</svg>

Rule 1: class becomes className

class is a reserved word in JavaScript, so React uses the DOM property name className. This is the same rule as in HTML elements. for on a label becomes htmlFor for the same reason, although it rarely appears in SVG.

Rule 2: hyphenated presentation attributes become camelCase

SVG presentation attributes such as stroke-width are written in camelCase in JSX. Remove each hyphen and capitalize the next letter:

One case is easy to misread. The clipPath prop on a shape is the old clip-path attribute. The <clipPath> element that defines the clipping shape is a different thing. Its tag name is already camelCase in SVG, and it stays that way.

Rule 3: namespaced attributes lose the colon

JSX attribute names cannot contain a colon in React, so namespaced names are folded into camelCase: xlink:href becomes xlinkHref, xml:space becomes xmlSpace, and xmlns:xlink becomes xmlnsXlink.

In new code you rarely need the xlink forms. SVG 2 deprecates xlink:href, and current browsers support a plain href on use, image, gradients, and patterns. Writing href="#g" lets you drop the xmlnsXlink declaration as well.

Rule 4: some names already match

Several SVG attributes are already camelCase in the specification, and React uses them as they are:

aria-* and data-* attributes are the deliberate exception to Rule 2. React keeps them hyphenated, exactly as in HTML. role stays as it is.

Rule 5: style is an object

In JSX, style takes a JavaScript object rather than a CSS string. Property names become camelCase, and the value of each property is a string or a number. CSS custom properties keep their -- prefix, so they have to be written as quoted keys:

style attribute
// SVG
<g style="stroke-dasharray: 4 2; --accent: #0f766e">

// JSX
<g style={{ strokeDasharray: "4 2", "--accent": "#0f766e" }}>

React adds px to plain numbers for most length properties when it sets inline styles, and it leaves unitless properties such as opacity alone. In SVG, many lengths are user units anyway, so converters usually keep the values as written. In TypeScript, a custom property key needs a cast or an extended type, because CSSProperties does not list them.

Rule 6: numbers can stay strings

SVGR turns attribute values that are plain numbers into numbers, so width="24" becomes width={24}. It does this even for data-x. React converts the value back to a string when it sets the attribute, so both forms render the same. Do not convert values that contain units or lists. "1.5em", "0 0 24 24", and path data must stay strings.

Rule 7: XML extras that JSX does not allow

The XML declaration (<?xml … ?>) and DOCTYPE lines are not JSX, so remove them. HTML comments are a syntax error inside JSX. Delete them, or turn them into {/* … */} comments. Every element must be closed, so <path> without a closing tag becomes <path />. Exporters already write empty elements this way.

Braces in text content are also special, because JSX reads them as expressions. A label that contains literal braces has to be wrapped in a string:

Braces in text
// Invalid JSX: the braces start an expression
<text x="0" y="16">{count}</text>

// Literal braces in the label
<text x="0" y="16">{"{count}"}</text>

Rule 8: event attributes and scripts do not carry over

SVG files can contain onclick attributes and <script> elements. React event props take functions, not strings, so a string handler copied into JSX does not work. More importantly, you should not ship script from an SVG you did not write. Remove inline handlers and scripts, and attach behavior with React instead:

Events
// SVG from a file. Not valid in React.
<circle onclick="toggle()" r="4" />

// React. The handler is a function, not a string.
<circle onClick={toggle} r={4} />

The converters on this site go one step further. They sanitize the file first and remove scripts, event handler attributes, foreignObject, and external URLs before SVGR runs. The details are in What the SVG Sanitizer Removes First.

Rule 9: other DOM renames

A few attributes follow the HTML-to-DOM rules rather than the SVG ones. tabindex becomes tabIndex, as in the example output above. crossorigin on an image element becomes crossOrigin. If you are unsure, render the component in development and read the console. React names the expected prop in its warning.

What not to rename

Over-correcting causes bugs too. Do not rename viewBox to viewbox. Do not add camelCase to aria-label. Do not change text inside <text> or <title>, even if it happens to contain an attribute name. A converter that parses the file leaves text alone. A search-and-replace does not, which is the main reason to prefer a parser.

Frameworks that compile templates rather than JSX keep the SVG names. The SVG to Vue and SVG to Svelte tools leave class and stroke-width exactly as they are. For a React project, paste the file into the SVG to JSX converter, or read SVG to React Component: A Practical Guide for the steps that come after renaming.

Questions

Why does React accept aria-label but not stroke-width?

React keeps aria-* and data-* attributes in their hyphenated HTML form on purpose. Other SVG attributes are mapped from camelCase prop names, so React expects strokeWidth and logs a development warning that suggests the camelCase name when it sees stroke-width.

Should I keep xlinkHref or switch to href?

SVG 2 deprecates xlink:href in favor of a plain href attribute, and current browsers support href on use, image, and gradient elements. React accepts both. Use href in new code and keep xlinkHref only if you must support a renderer that still needs it.

Does viewBox need to change in JSX?

No. viewBox is already written in camelCase in SVG, and React uses that exact name. The same is true for preserveAspectRatio, gradientUnits, and patternUnits. Writing viewbox in lowercase is the mistake to watch for when SVG is typed by hand.

What happens to a style attribute during conversion?

JSX needs an object, so style="fill-opacity:.5;stroke-linecap:round" becomes style={{ fillOpacity: 0.5, strokeLinecap: "round" }}. Property names become camelCase and plain numbers become numbers. CSS custom properties keep their -- prefix as quoted keys.