Logo
Back to Blog
Development August 8, 2026 8 min read

Why stroke-width Breaks in React (and Every Other SVG Attribute)

O

OmniWebKit Team

Frontend Engineering

Share:
Article Cover Image

You paste an icon from Figma into a React component. The build fails, or worse, it renders with hairline strokes and no error. The culprit is one hyphen, and it appears in nearly every SVG a design tool exports.

Why SVG Attributes in React Need camelCase

Because JSX is JavaScript, and a hyphen means subtraction there.

JSX looks like HTML but compiles into JavaScript function calls. Every attribute becomes a property on an object.

JavaScript identifiers cannot contain hyphens. Write stroke-width and the parser sees stroke minus width — two variables and an operator.

So React uses strokeWidth. Same attribute, name that JavaScript can hold.

Our SVG to JSX converter rewrites all of them in one pass, if you would rather not do it by hand.

The stroke-width JSX Error and Its Cousins

Most of the time it does not throw — it warns. That is what makes it slow to find.

React passes attributes it does not recognise straight to the DOM. The element renders, the property never applies, and your icon looks subtly wrong.

Here is the full conversion table:

SVG attributeJSX propertyUsed for
stroke-widthstrokeWidthOutline thickness
stroke-linecapstrokeLinecapLine end shape
stroke-linejoinstrokeLinejoinCorner shape
stroke-dasharraystrokeDasharrayDashed outlines
stroke-miterlimitstrokeMiterlimitSharp corner cutoff
fill-rulefillRuleInterior fill algorithm
fill-opacityfillOpacityFill transparency
clip-pathclipPathClipping region
clip-ruleclipRuleClipping algorithm
stop-colorstopColorGradient stops
stop-opacitystopOpacityGradient transparency
flood-opacityfloodOpacityFilter flood effects
text-anchortextAnchorText alignment
font-familyfontFamilyText font
letter-spacingletterSpacingCharacter spacing
classclassNameCSS classes

The pattern is consistent: drop the hyphen, capitalise what followed it.

The clip-path Trap That Survives Conversion

Renaming the attribute is only half the job.

A clipPath value is usually a reference: url(#mask-a). That points at an id defined elsewhere in the same file.

Convert two icons that both export with id="mask-a", render both on one page, and you now have duplicate ids. One silently wins and the other icon clips wrong.

In our experience this is the hardest SVG bug to diagnose, because each icon works perfectly on its own. Rename ids to something unique per icon before shipping.

Three Attributes That Follow Different Rules

Not everything hyphenated becomes camelCase. Three exceptions worth knowing:

  • data-* and aria-* keep their hyphens. React passes them straight through, so data-testid and aria-label stay as written.
  • Already-camelCase attributes such as viewBox, preserveAspectRatio and gradientTransform stay exactly as they are. Lowercasing them breaks the element.
  • Namespaced attributes use a colon, not a hyphen. xlink:href becomes xlinkHref, and xml:space becomes xmlSpace.

That last group catches converters that only handle hyphens. If your icon references a symbol and stopped working, check the xlink attribute first.

Inline Styles Need an Object, Not a String

This one throws rather than warns.

// Breaks
style="fill: red; stroke-width: 2"

// Works
style={{ fill: 'red', strokeWidth: 2 }}

Double braces confuse people the first time. The outer pair means "JavaScript expression", the inner pair is the object itself.

Keys are camelCased here too, for the same reason as everywhere else.

Worth noting for comparison: Vue accepts the string exactly as your exporter wrote it, which is why the SVG to Vue component converter has far less to do.

Let TypeScript Catch It Instead

A wrong attribute becomes a compile error rather than a console warning.

That difference is larger than it sounds. Console warnings scroll past during development and nobody reads them before a deploy.

On an icon-heavy codebase we would use .tsx for this reason alone. Our SVG to TSX converter generates the typed props interface for you.

One honest caveat: TypeScript catches unknown attributes, not wrong values. A strokeWidth of the wrong number still type-checks.

Wrapping Up

SVG attributes in React need camelCase because JSX compiles to JavaScript and JavaScript identifiers cannot hold a hyphen. That is the whole rule.

Remember the exceptions: data and aria keep their hyphens, already-camelCase attributes stay untouched, and namespaced ones use a different pattern.

For the wider picture, see the seven ways to use SVG in React, or automate the whole thing with SVGR or an online converter.

Frequently Asked Questions

Why does React warn instead of failing when I get an attribute wrong?

+
Because JSX passes unknown attributes through to the DOM rather than rejecting them. The element still renders, just without that property applied, so a stroke-width typo produces a warning and a wrong-looking icon.

Is data-something or aria-something meant to stay hyphenated?

+
Yes, and they are the exception. React passes data and aria attributes straight through, so data-testid and aria-label keep their hyphens. Every other hyphenated attribute needs camelCase.

Why is it className and not class?

+
Because class is a reserved word in JavaScript, so it cannot be an object property name. The same reason gives you htmlFor instead of for. Neither has anything to do with SVG specifically.

What about attributes that were already camelCase, like viewBox?

+
Leave them exactly as they are. viewBox, preserveAspectRatio, clipPathUnits and gradientTransform are camelCase in the SVG spec itself. Lowercasing them breaks the element.

Why does my clipPath reference stop working after conversion?

+
Check the id, not the attribute. The clip-path value is a url reference to an id elsewhere in the file, and bundlers that inline multiple icons can produce duplicate ids. Two elements with the same id means one silently wins.

Is xlink:href deprecated?

+
Yes, SVG 2 replaced it with a plain href and browser support is good. Older exports still emit it, so converters keep handling it. If you control the source, switch to href and skip the problem.

Can I keep the style attribute as a string?

+
Not in JSX. React expects an object with camelCased keys, so a raw style string throws. Vue accepts the string as written, which is why the same file needs less work there.

Why do numeric values sometimes need braces?

+
They do not have to, since strings coerce fine. Braces matter when you pass a variable or an expression. Mixing the two styles in one file is legal but reads badly in review.

Do these rules change in React 19?

+
The core camelCase requirement has not changed, because it comes from JavaScript identifier rules rather than React policy. React 19 did relax handling of some custom attributes, but nothing that removes the renaming.

Does TypeScript catch these mistakes?

+
Much better than plain JavaScript does. A wrong attribute becomes a type error instead of a console warning you scroll past, which is a genuine argument for .tsx on an icon-heavy codebase.

Tags

#React#SVG#JSX#Debugging