Product teams should be thinking differently about documents.Read blog post.
Anvil Logo
Products
Industries
Resources
Developers
Engineering

Peace of mind with inline SVGs in React: eslint-plugin-svg-jsx

Author headshot
By Derek Foster

Using inline SVG images in your HTML comes with a lot of benefits, but can be a DX pain in React. Learn how to use ESLint to automate your SVG props and develop UIs like a breeze.

Back to all articles
Peace of mind with inline SVGs in React: eslint-plugin-svg-jsx

Last year, we wrote about camel casing inline SVG props in React, why inline SVGs are a good performance win in the browser, and how to avoid annoying browser warnings when converting an SVG image to a React component.

Since then, we've learned of a couple more cases that make converting SVG images to React components annoying to deal with. I recommend reading our "Automated camel cased props for React" blog post linked above for more context on the motivation for automating these cases before reading this post.

Let's get right into all of the cases and how we've automated them in the new eslint-plugin-svg-jsx package.

  1. Changing dashed cased attributes into camel cased props (e.g. fill-rule="value" becomes fillRule="value")*
  2. Changing colon cased attributes into camel cased props (e.g. xmlns:xlink="value" becomes xmlnsXlink="value")
  3. Changing style strings to style props (e.g. style="margin: 20px" becomes style={{ margin: '20px' }})

*Camel casing dashed cased attributes works the same as it did in eslint-plugin-react-camel-case package we released last year.

Dashed cased attributes

This was the original motivation for creating an ESLint plugin to recognize and fix issues with inline SVGs. Since React is actually JavaScript and not HTML, camel casing props is easier to process. It's the same reason when accessing a key in JS object, you prefer using the syntax myObj.camelCasedKey instead of the syntax myObj['snake-cased-key']. React as a library just enforces camelCasing to avoid any ambiguity on this topic.

One interesting case about React's camel casing is the className prop. This one isn't to avoid dashs in the React prop because the original HTML attribute is simply class. The reasoning is that class is a reserved keyword in JavaScript, so React needed a way to differentiate the prop from keyword class.

Enough historical context - dashed cased attributes (same thing as snake-cased attributes) are bad news in React land! The new eslint-plugin-svg-jsx will find and automatically fix those attributes to turn them into proper camel cased props. Since this was the original case from the camel casing inline SVG props in React blog post, I recommend reading that for more context if you'd like to (not needed, just read the get started section and profit right now 🤑)

Colon cased attributes

The first of the new cases added is colon-cased attributes in SVGs. In SVG/HTML/XML, colons denote a namespace. They are out of scope for this post, besides me saying it's really ironic that React requires us to camel case them, just so React can then un-camel case them when actually writing to the DOM. Is it dumb we have to do this? Absolutely. Is React and all of it's benefits worth this small dumb convention? Also absolutely!

Where colon cased props break down in React is with embedded PNGs within SVGs. Here's an example of an SVG with a PNG embedded inside of it:

<svg
  width="423"
  height="882"
  viewBox="0 0 423 882"
  fill="none"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
>
  <defs>
    <pattern
      id="pattern0"
      patternContentUnits="objectBoundingBox"
      width="1"
      height="1"
    >
      <use
        xlink:href="#image0_1834_10713"
        transform="scale(0.000483092 0.000724638)"
      />
    </pattern>
    <image
      id="image0_1834_10713"
      width="2070"
      height="1380"
      xlink:href="data:image/png;base64,dahsdsahdasuu..."
    />
  </defs>
</svg>

Base 64'd PNG inside an SVG element, with the id of "image0_1834_10713"

While this is valid HTML, it will cause a big problem if you move it to React without camel casing the prop: React won't find the image you are referencing inside the pattern element. The element <use xlink:href="#image0_1834_10713" transform="scale(0.000483092 0.000724638)" /> will have a reference to the image element with id image0_1834_10713, but no way to actually find it! Which means... no image will be shown to the user, and your asset will be horribly incorrect.

Instead of catching this by hand when you set up your inline SVG, eslint-plugin-svg-jsx will recognize this incorrect prop and camel case it for you. The correct, React version of the SVG:

export default (props) => (
  <svg
    width="423"
    height="882"
    viewBox="0 0 423 882"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    xmlnsXlink="http://www.w3.org/1999/xlink"
    {...props}
  >
    <defs>
      <pattern
        id="pattern0"
        patternContentUnits="objectBoundingBox"
        width="1"
        height="1"
      >
        <use
          xlinkHref="#image0_1834_10713"
          transform="scale(0.000483092 0.000724638)"
        />
      </pattern>
      <image
        id="image0_1834_10713"
        width="2070"
        height="1380"
        xlinkHref="data:image/png;base64,dahsdsahdasuu..."
      />
    </defs>
  </svg>
)

Camel cased props (xlinkHref) compatible with React elements instead of colon cased attributes

With the above, React-compatible SVG component, your UI will be stellar and with great performance ⚡️

Style strings

The last case of the 3 is style strings. In my opinion, this one is the most annoying case because React won't even work if you get this wrong - a build error will occur, and the flow state you were in during development comes to a screeching halt.

One of the benefits of inline SVG over using them with an img tag is that you have full control over styling and attaching event listeners to child tags of SVGs... but that also means they can come with the style attribute already set. For example:

<mask
  id="mask0_1834_10713"
  style="mask-type: alpha"
  mask-units="userSpaceOnUse"
  x="48"
  y="87"
  width="50"
  height="50"
>
  <circle cx="72.8218" cy="111.822" r="24.8218" fill="#D9D9D9" />
</mask>

Not ideal. The mask element on our SVG comes with a builtin style via the style attribute, but React style props need to be an object.

Inline style attributes like the above are considered bad practice in web development as they make targeting elements with CSS difficult and can produce unexpected behavior. But when you export an SVG from a tool like Figma, they can often appear directly on a mask like this because the mask element is unique to SVGs, making it essentially non-targetable by most CSS.

In any case, inline style props in React are actually much more common because of the component-based nature of React. Elements in React are already broken up by logical boundaries (e.g. being an element in a component), so inline styles will apply to all instances of the component/element they are used on. For the purposes of this blog post: all we need to do is convert the string style attribute on the SVG into an object for the React element's style prop. eslint-plugin-svg-jsx handles that as well!

<mask
  id="mask0_1834_10713"
  style={{ maskType: 'alpha' }}
  maskUnits="userSpaceOnUse"
  x="48"
  y="87"
  width="50"
  height="50"
>
  <circle cx="72.8218" cy="111.822" r="24.8218" fill="#D9D9D9" />
</mask>

Ideal! The style prop has been converted to an object, making React happy which makes us happy 😊

Get started

  1. Add the dependency: yarn add -D eslint-plugin-svg-jsx or npm install --save-dev eslint-plugin-svg-jsx
  2. In your .eslintrc.js:
    1. Add svg-jsx to your plugins
    2. Add the svg-jsx rules:
    'svg-jsx/camel-case-dash': 'error',
    'svg-jsx/camel-case-colon': 'error',
    'svg-jsx/no-style-string': 'error',

Your final .eslintrc.js should look something like:

module.exports = {
  parser: "@babel/eslint-parser",
  extends: ["standard", "standard-jsx", "plugin:prettier/recommended"],
  plugins: ["no-only-tests", "prettier", "svg-jsx"],
  rules: {
    "svg-jsx/camel-case-dash": "error",
    "svg-jsx/camel-case-colon": "error",
    "svg-jsx/no-style-string": "error",
  },
}

Added svg-jsx to the plugins array and all the svg-jsx rules to the rules object

A few things of note

.eslintrc.js is just one of many file formats to use with ESLint. We prefer JS since that is the language we use and is more inherently customizable than something like json or yml, but use the config format you prefer.

The 'automated' part of this plugin comes from ESLint's fix errors feature. Your IDE will give you the option to fix it while you are editing code, or you can run eslint with the --fix option, e.g. eslint src --fix will automatically fix these issues for you in all JS files within the src directory.

At Anvil, we have taken the automation a step further by introducing pre-commit hooks to Git, so we ensure we are always checking in code that has been linted properly. I recommend this article to see how you can also set up pre-commit hooks in your project.

Time to build some beautiful UIs

Now that you recognize some of the annoying cases with inline SVGs in React, time to forget them and use eslint-plugin-svg-jsx. I've personally saved days of work and many headaches from using the plugin over the course of a few months. I hope you'll find as much use out of the plugin as I have, so let's get back to building the web in a fast & fun way! If you have any other interesting cases you think the plugin should cover, we'd love to hear from you at developers@useanvil.com, or feel free to contribute in the GitHub repo.

Sign up for a live demo

Request a 30-minute live demo today and we'll get in touch shortly. During the meeting our Sales team will help you find the right solution, including:
  • Simplifying data gathering
  • Streamlining document preparation
  • Requesting e-signatures
  • Building and scaling your business
Want to try Anvil first?Sign up for free
Want to try Anvil first?Sign up for free