BlogSVG customization (SVGR)

SVG customization (SVGR)

Next.js

React

SVG

Front-end

Language : English

by Rayane Merlin

30/10/2025

Hello there ! Today, we're gonna explore how I was able to customize SVGs on my web project WorldMaster. We're gonna see in the first step the made customization applied to SVGs to make you better understand the process.

Before you begin 📝

Prerequisites

What is SVGR ?

SVGR is a tool that transforms SVGs into React components. This allows us to easily manipulate and customize SVGs using React's props and state. In our case, this library was included in the next.js configuration to permit to keep control over all SVGs parts. Initially, importing an SVG in React would treat it as a static file as the source of

<img/>
, limiting our ability to customize it because all SVG parts would be inaccessible (in html and javascript).

SVGR Icon

SVGR is a tool that transforms SVGs into React components. This allows us to easily manipulate and customize SVGs using React's props and state.

There's a lot of options to configure SVGR to fit your needs. In our case, we used the default with some modifications in the next.config.mjs file to allow importing SVGs as React components.

/** @type {import('next').NextConfig} */
const nextConfig = {
  ...
  webpack(config) {
    // Grab the existing rule that handles SVG imports
    const fileLoaderRule = config.module.rules.find((rule) =>
      rule.test?.test?.('.svg'),
    )
    fileLoaderRule.exclude = /\.svg$/i;

    config.module.rules.push(
      {
        test: /\.svg$/i,
        use: [{
          loader: '@svgr/webpack',
          options: {
            svgo: true,
            svgoConfig: {
              plugins: [
                /* Initially, we had these options to clean up the SVGs but for our
                customization needs, we disabled them. Each colorable part needed to keep
                its id and className without any prefix. We will detail this later.*/
                { name: 'prefixIds',
                  params: { prefixIds: false, prefixClassNames: false, prefix: false } },
                { name: 'cleanupIds', params: { remove: false, minify: false, prefix: '' } },
              ]
            },
          },
        }],
      },
    );
    return config;
  },
};

Reminder

All SVGs used were taken from an open source repository on Github called flag-icons. Initially without any interactivity.

Right now, let's see how we passed from a static SVG to a customized one, clickable and listing its functionalities.

Clickable SVGs & Shapes Background Image

Here is an example of a clickable SVG flag. On the right, the original SVG without any customization. And on the left the final customized and interactive SVG.

SVG Customization Example

⚠️ Important to know :

import styles from '.../style.module.scss';

// { ... React component code ... }
const initShapes = () => {

  const acceptedTags: string[] = ['path', 'circle', 'g', 'rect'];
  const allShapes: NodeListOf<SVGElement> = svgContainer.current?
    .querySelectorAll(acceptedTags.map((selector: string) => 
      (`svg:not(.${logoStyles.appLogoSvg}) ${selector}[fill]:not(defs *)`)).join(', '));
  const colorableShapesTmp: Shape[] = [];

  let flagColors: string[] = [];
  allShapes.forEach((shape: SVGElement) => {

    if ((shape.classList.contains("keep"))) {

      const initialFill: string | null = shape.getAttribute('fill');
      if (initialFill !== null && initialFill !== 'none' && !initialFill.startsWith("url")) {

        const rgbInitialFill: string = hexToRgb(initialFill);
        shape.setAttribute('fill', 'url(#pngPattern)');
        shape.setAttribute('data-fill', rgbInitialFill);
        shape.classList.add(styles.svgContent);
        colorableShapesTmp.push(shape);

        if (!flagColors.some((color) => (rgbInitialFill === color))) {
          flagColors.push(rgbInitialFill);
        }

      }

    }
    
  });
};

In this code snippet, we select all the SVG elements that can be colored (like

<path>
,
<circle>
, etc.) and check if they have a
fill
attribute. If they do, we store their initial fill color in a
data-fill
attribute and change their
fill
to a pattern defined in the SVG's
<defs>
. This allows us to later manipulate the fill color dynamically. You can see that we used a new attribute for the fill
url(#pngPattern)
. This pattern is defined in the SVG's
<defs>
section and uses a background image (in our case a PNG image) to fill the shapes. This technique allows us to create complex fills using images.

There's the component used to define the SVG pattern with the background image. We set the image source to a PNG file located in the public folder.

interface SvgPatternInterface 
  { x: number, y: number, patternWidth: number, patternHeight: number }

const SvgPattern = ({ x, y, patternWidth, patternHeight }: SvgPatternInterface) => (
  <defs>
    <pattern xmlns="http://www.w3.org/2000/svg" id="pngPattern" patternUnits="userSpaceOnUse" width={ patternWidth } height={ patternHeight }>
      <image xmlnsXlink="http://www.w3.org/1999/xlink" 
        {/* Set the initial background with the png (public/) */}
        xlinkHref={"/images/patterns/png-background.jpg" } 
        x={ x } y={ y } width={ imageWidth } height={ imageHeight }
      />
    </pattern>
  </defs>
);

This

<SvgPattern/>
component defines a pattern that can be used to fill SVG shapes. The pattern uses an image located at
/images/patterns/png-background.jpg
as its fill. The
patternWidth
and
patternHeight
props allow us to control the size of the pattern.

Adding this pattern after rendered directly in the SVG component permit to have all shapes filled with the background image. And this is why we need to handle SVGs as React components, to be able to manipulate their structure and attributes.

Here's how we insert the

<defs>
section with the pattern into the SVG after rendering it :

const SvgPattern: ReactNode = (<SvgPattern
    pattern={pattern}
/>);

const svgPatternString: string = renderToString(SvgPattern);
colorableSvgElement.insertAdjacentHTML('afterbegin', svgPatternString);

The rendered SVG now has a pattern defined in its

<defs>
section, which is used to fill the shapes with the background image. Here's a simplified example of what the final SVG structure looks like after adding the pattern:

<svg ... >
  <defs>
    <pattern id="pngPattern" patternUnits="userSpaceOnUse" width="800" height="600">
      <image xlink:href="/images/patterns/png-background.jpg" x="0" y="0" width="1600" height="1200"/>
    </pattern>
  </defs>
  <path fill="url(#pngPattern)" data-fill="rgb(255, 0, 0)" ... />
  <circle fill="url(#pngPattern)" data-fill="rgb(0, 0, 255)" ... />
</svg>

Finally, we can add interactivity to the SVG by attaching

onClick
event handlers to the shapes. When a shape is clicked, we can retrieve its original fill color from the
data-fill
attribute and perform any desired actions, such as updating the UI or changing the shape's appearance.

const handleShapeClick = (event: MouseEvent<SVGElement>) => {
  const target = event.currentTarget;
  const originalFill = target.getAttribute('data-fill');
  if (!originalFill) throw new Error("No original fill found");
  target.setAttribute('fill', originalFill);
};

SVG files initialization

To make all this work, we had to modify the original SVG files a bit. Since we wanted to allow users to click on different parts of the SVG and see their original colors, we needed a way to identify which parts should be interactive. Taking the flag of Mexico as an example, we added a specific class "keep" to all the SVG elements that we wanted to be clickable and have their colors changeable. This is why we removed the svg class prefixes in the NextJS Config file. This class acts as a marker for our JavaScript code to identify which parts of the SVG should be processed. The problem with this method is that it requires manual modification of each SVG file to add the "keep" class to the desired elements. This can be time-consuming, especially if you have a large number of SVGs to process. A simple solution for this was to do it manually with creating a new route in the project to display all SVGs and edit them directly, allowing to see the changes in real-time (svg structure hot reloading).

Dev Page to configure SVGs

After selecting all colorable parts for each flag, we obtained SVGs with the necessary classes added. This allowed our JavaScript code to easily identify and manipulate the correct parts of the SVGs during runtime.

There's an example of the Mexico flag SVG before and after adding the classes to the colorable shapes. The middle part containing the eagle was initially selectable (left), and left unchanged as it doesn't need to be interactive (right).

Utility of Shapes Selection

After that, our JavaScript code can simply look for elements with the "keep" class and apply the necessary logic to make them interactive and customizable.

Final Thoughts & Recap

In this blog post, we explored how to customize SVGs in a React/Next.js project using SVGR, from its configuration to its usage. We learned how to import SVGs as React components, manipulate their structure, and add interactivity by attaching event handlers to specific parts of the SVG.

We also discussed the importance of modifying the original SVG files to add specific classes to the elements we wanted to be interactive. This allowed us to easily identify and manipulate those parts during runtime.

By following the steps outlined in this post, you can create dynamic and interactive SVGs that enhance the user experience on your web projects. Whether you're building a flag selector, an icon library, or any other application that involves SVG graphics, the techniques discussed here will help you achieve your goals. There's an example in NextJS for the final result of Mexico Colorable Flag on WorldMaster, but feel free to experiment and adapt these techniques to your own projects!