Dynamic SVG images using Next.js

This page summarizes the projects mentioned and recommended in the original post on dev.to

Our great sponsors
  • SurveyJS - Open-Source JSON Form Builder to Create Dynamic Forms Right in Your App
  • InfluxDB - Power Real-Time Data Analytics at Scale
  • WorkOS - The modern identity platform for B2B SaaS
  • svgo

    ⚙️ Node.js tool for optimizing SVG files

  • In addition to the techniques we’ve discussed so far, there are optimization tools available that can further enhance SVG images. These tools, such as SVGO and ImageOptim, offer valuable features to reduce file size and clean up SVG markup, making it easier to standardize and optimize the overall performance of SVG assets.

  • blog-articles

  • As we delve into this discussion, it’s important to know that we’ve conveniently made the solutions discussed in this article accessible via our GitHub repository. This will allow you to engage more practically with the topics we explore.

  • SurveyJS

    Open-Source JSON Form Builder to Create Dynamic Forms Right in Your App. With SurveyJS form UI libraries, you can build and style forms in a fully-integrated drag & drop form builder, render them in your JS app, and store form submission data in any backend, inc. PHP, ASP.NET Core, and Node.js.

    SurveyJS logo
  • Next.js

    The React Framework

  • In this example, we’ve added id properties to all circles, allowing us to target them individually within the tag. I’ve defined two “color modes” called oceanBreeze and sunsetGlow. You can set the color mode by adding a data-color-mode property to the element. To preview the SVG and see the live result, you can install an SVG preview plugin for VSCode or use other similar tools. For example, to view the SVG with the oceanBreeze color mode, modify the code as follows:

     viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" data-color-mode="oceanBreeze">
        
    
    
    Enter fullscreen mode Exit fullscreen mode

    ocean breeze dots

    To control the color mode using React, let’s create a new page.tsx in a directory named circles-inline-style:

    import CirclesInlineStyle from "./CirclesInlineStyle.svg";
    
    export default function CirclesInlineStylePage() {
      return <CirclesInlineStyle data-color-mode="sunsetGlow" />;
    }
    
    Enter fullscreen mode Exit fullscreen mode

    The resulting SVG will display the circles with the expected colors:

    sunset glow dots

    ⚠ Be cautious when using inline styles, as different browsers may produce varying results. Make sure to test carefully. Additionally, setting styles on elements like or using advanced CSS may lead to cross-browser issues. Sticking to basic styles will help avoid these problems.

    2) Using Next.js’ built-in CSS modules

    To implement this approach, let’s create a new page in a directory called circles-css-module. Within this directory, we’ll create a new CSS module file named circles.module.css:

    /** Ocean breeze */
    .oceanBreeze [data-node-id="top-right-circle"] {
      fill: DeepSkyBlue;
    }
    
    .oceanBreeze [data-node-id="top-left-circle"] {
      fill: MediumAquaMarine;
    }
    
    .oceanBreeze [data-node-id="bottom-right-circle"] {
      fill: CornflowerBlue;
    }
    
    /** Sunset glow */
    .sunsetGlow [data-node-id="top-right-circle"] {
      fill: Coral;
    }
    
    .sunsetGlow [data-node-id="top-left-circle"] {
      fill: Gold;
    }
    
    .sunsetGlow [data-node-id="bottom-right-circle"] {
      fill: DarkOrange;
    }
    
    Enter fullscreen mode Exit fullscreen mode

    In this CSS module file, we define different selectors for the color modes “oceanBreeze” and “sunsetGlow”. Instead of using the id selector, we use attribute selectors with data-node-id. This is because the SVGR loader removes any id attributes from elements when inline styles are not used, to avoid collisions with the parent HTML document.

    Next, let’s create a new SVG file named CirclesCssModule.svg in the same directory. This SVG file will be identical to the one used in the inline style approach but without the </code> tag. Also, we’ll use <code>data-node-id</code> attributes instead of <code>id</code> to identify the circles:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight xml"><code><span class="nt"><svg</span> <span class="na">viewBox=</span><span class="s">"0 0 200 200"</span> <span class="na">xmlns=</span><span class="s">"http://www.w3.org/2000/svg"</span><span class="nt">></span> <span class="nt"><g</span> <span class="na">transform=</span><span class="s">"matrix(1,0,0,1,0,0)"</span><span class="nt">></span> <span class="c"><!-- Top-left circle --></span> <span class="nt"><circle</span> <span class="na">data-node-id=</span><span class="s">"top-left-circle"</span> <span class="na">fill=</span><span class="s">"black"</span> <span class="na">cx=</span><span class="s">"50"</span> <span class="na">cy=</span><span class="s">"50"</span> <span class="na">r=</span><span class="s">"40"</span> <span class="nt">/></span> <span class="c"><!-- Top-right circle --></span> <span class="nt"><circle</span> <span class="na">data-node-id=</span><span class="s">"top-right-circle"</span> <span class="na">fill=</span><span class="s">"black"</span> <span class="na">cx=</span><span class="s">"150"</span> <span class="na">cy=</span><span class="s">"40"</span> <span class="na">r=</span><span class="s">"30"</span> <span class="nt">/></span> <span class="c"><!-- Bottom-right circle --></span> <span class="nt"><circle</span> <span class="na">data-node-id=</span><span class="s">"bottom-right-circle"</span> <span class="na">fill=</span><span class="s">"black"</span> <span class="na">cx=</span><span class="s">"150"</span> <span class="na">cy=</span><span class="s">"150"</span> <span class="na">r=</span><span class="s">"20"</span> <span class="nt">/></span> <span class="nt"></g></span> <span class="nt"></svg></span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>Now, let’s create a new <code>page.tsx</code> file where we can import the SVG component and apply the CSS module:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="nx">CirclesCssModule</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./CirclesCssModule.svg</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">styles</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./circles.module.css</span><span class="dl">"</span><span class="p">;</span> <span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">CirclesCssModulePage</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p"><</span><span class="nc">CirclesCssModule</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">styles</span><span class="p">.</span><span class="nx">sunsetGlow</span><span class="si">}</span> <span class="p">/>;</span> <span class="p">}</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>In this example, we import the <code>CirclesCssModule</code> component from the <code>CirclesCssModule.svg</code> file. We also import the generated CSS module file <code>styles</code> that contain the class names for the color modes.</p> <p>By applying the <code>styles.sunsetGlow</code> class name as a prop to the <code>CirclesCssModule</code> component, we can dynamically set the color mode.</p> <p>The resulting SVG will appear the same as the inline style approach but with the advantage of being able to separate the CSS into a style sheet. This means you can utilize additional features like <a href="https://nextjs.org/docs/app/building-your-application/styling/sass">Sass</a>, which is not natively supported within SVG files.</p> <p>However, one drawback of this approach is that you can no longer preview the styles using standard preview tools since React is required to apply the styles dynamically.</p> <h4> <a name="3-using-a-helper-function" href="#3-using-a-helper-function"> </a> 3) Using a helper function </h4> <p>If predefining styles isn’t an option for you and you’re seeking greater flexibility, using a wrapping component may be your only remaining option. We’ve already created such a component to demonstrate how this can be achieved. However, please note that due to the nature of SVGR imports, this is not entry-level code. You can create the helper function under the <code>/src</code> directory and name it <code>SvgController.ts</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="kd">type</span> <span class="nx">ExtendedSVGProps</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">SVGProps</span><span class="o"><</span><span class="nx">SVGSVGElement</span><span class="o">></span> <span class="o">&</span> <span class="p">{</span> <span class="p">[</span><span class="na">attr</span><span class="p">:</span> <span class="kr">string</span><span class="p">]:</span> <span class="kr">string</span><span class="p">;</span> <span class="p">};</span> <span class="kd">type</span> <span class="nx">ControlRule</span> <span class="o">=</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="p">{</span> <span class="na">attributeName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">attributeValue</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="p">};</span> <span class="nl">props</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">SVGProps</span><span class="o"><</span><span class="nx">SVGSVGElement</span><span class="o">></span><span class="p">;</span> <span class="p">};</span> <span class="kd">type</span> <span class="nx">ControlRules</span> <span class="o">=</span> <span class="nx">ControlRule</span><span class="p">[];</span> <span class="cm">/** * Get an object that indexes control rules. * * @param rules - The control rules. * * @returns An object that indexes control rules. */</span> <span class="kd">const</span> <span class="nx">getPropsSelectorIndex</span> <span class="o">=</span> <span class="p">(</span> <span class="nx">rules</span><span class="p">:</span> <span class="nx">ControlRules</span> <span class="p">):</span> <span class="p">{</span> <span class="p">[</span><span class="nx">key</span><span class="p">:</span> <span class="kr">string</span><span class="p">]:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">SVGProps</span><span class="o"><</span><span class="nx">SVGSVGElement</span><span class="o">></span> <span class="p">}</span> <span class="o">=></span> <span class="p">{</span> <span class="k">return</span> <span class="nx">rules</span><span class="p">.</span><span class="nx">reduce</span><span class="p">((</span><span class="nx">acc</span><span class="p">,</span> <span class="nx">config</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="nx">attributeName</span><span class="p">,</span> <span class="nx">attributeValue</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">config</span><span class="p">.</span><span class="nx">selector</span><span class="p">;</span> <span class="nx">acc</span><span class="p">[</span><span class="nx">attributeName</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">:</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">attributeValue</span><span class="p">]</span> <span class="o">=</span> <span class="nx">config</span><span class="p">.</span><span class="nx">props</span><span class="p">;</span> <span class="k">return</span> <span class="nx">acc</span><span class="p">;</span> <span class="p">},</span> <span class="p">{}</span> <span class="k">as</span> <span class="p">{</span> <span class="p">[</span><span class="na">key</span><span class="p">:</span> <span class="kr">string</span><span class="p">]:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">SVGProps</span><span class="o"><</span><span class="nx">SVGSVGElement</span><span class="o">></span> <span class="p">});</span> <span class="p">};</span> <span class="cm">/** * Clone a React node and its children while trying to inject new props. * * @param node - The node to clone. * @param propsSelectorIndex - An object that indexes control rules. * * @returns The cloned node with new props when applicable. */</span> <span class="kd">const</span> <span class="nx">cloneNode</span> <span class="o">=</span> <span class="p">(</span> <span class="nx">node</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactElement</span><span class="o"><</span><span class="nx">ExtendedSVGProps</span><span class="o">></span><span class="p">,</span> <span class="nx">propsSelectorIndex</span><span class="p">:</span> <span class="p">{</span> <span class="p">[</span><span class="nx">key</span><span class="p">:</span> <span class="kr">string</span><span class="p">]:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">SVGProps</span><span class="o"><</span><span class="nx">SVGSVGElement</span><span class="o">></span> <span class="p">}</span> <span class="p">):</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactElement</span><span class="o"><</span><span class="nx">ExtendedSVGProps</span><span class="o">></span> <span class="o">=></span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="nx">children</span><span class="p">,</span> <span class="p">...</span><span class="nx">restProps</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">node</span><span class="p">.</span><span class="nx">props</span><span class="p">;</span> <span class="kd">let</span> <span class="na">nodeProps</span><span class="p">:</span> <span class="nb">Partial</span><span class="o"><</span><span class="nx">ExtendedSVGProps</span><span class="o">></span> <span class="o">&</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Attributes</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">restProps</span><span class="p">,</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">matchingProps</span> <span class="o">=</span> <span class="nx">propsSelectorIndex</span><span class="p">[</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">nodeProps</span><span class="p">).</span><span class="nx">find</span><span class="p">(</span> <span class="p">([</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">])</span> <span class="o">=></span> <span class="nx">propsSelectorIndex</span><span class="p">[</span><span class="nx">key</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">:</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">value</span><span class="p">]</span> <span class="p">)?.[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">:</span><span class="dl">"</span> <span class="o">+</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">nodeProps</span><span class="p">).</span><span class="nx">find</span><span class="p">(</span> <span class="p">([</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">])</span> <span class="o">=></span> <span class="nx">propsSelectorIndex</span><span class="p">[</span><span class="nx">key</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">:</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">value</span><span class="p">]</span> <span class="p">)?.[</span><span class="mi">1</span><span class="p">]</span> <span class="p">];</span> <span class="k">if</span> <span class="p">(</span><span class="nx">matchingProps</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">compatibleProps</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">matchingProps</span><span class="p">).</span><span class="nx">reduce</span><span class="p">(</span> <span class="p">(</span><span class="nx">acc</span><span class="p">,</span> <span class="p">[</span><span class="nx">propKey</span><span class="p">,</span> <span class="nx">propValue</span><span class="p">])</span> <span class="o">=></span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">propValue</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span> <span class="nx">acc</span><span class="p">[</span><span class="nx">propKey</span><span class="p">]</span> <span class="o">=</span> <span class="nx">propValue</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">acc</span><span class="p">;</span> <span class="p">},</span> <span class="p">{}</span> <span class="k">as</span> <span class="nx">ExtendedSVGProps</span> <span class="p">);</span> <span class="nx">nodeProps</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">nodeProps</span><span class="p">,</span> <span class="p">...</span><span class="nx">compatibleProps</span> <span class="p">};</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">clonedChildren</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Children</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">children</span><span class="p">,</span> <span class="p">(</span><span class="nx">child</span><span class="p">)</span> <span class="o">=></span> <span class="nx">React</span><span class="p">.</span><span class="nx">isValidElement</span><span class="o"><</span><span class="nx">ExtendedSVGProps</span><span class="o">></span><span class="p">(</span><span class="nx">child</span><span class="p">)</span> <span class="p">?</span> <span class="nx">cloneNode</span><span class="p">(</span><span class="nx">child</span><span class="p">,</span> <span class="nx">propsSelectorIndex</span><span class="p">)</span> <span class="p">:</span> <span class="nx">child</span> <span class="p">);</span> <span class="k">return</span> <span class="nx">React</span><span class="p">.</span><span class="nx">cloneElement</span><span class="p">(</span><span class="nx">node</span><span class="p">,</span> <span class="nx">nodeProps</span><span class="p">,</span> <span class="nx">clonedChildren</span><span class="p">);</span> <span class="p">};</span> <span class="cm">/** * Type guard to check if a React node is a functional component. * * @param node - A React node to check. * * @returns True if it's a functional component, otherwise false. */</span> <span class="kd">const</span> <span class="nx">isFunctionalComponent</span> <span class="o">=</span> <span class="p">(</span> <span class="nx">node</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactNode</span> <span class="p">):</span> <span class="nx">node</span> <span class="k">is</span> <span class="nx">React</span><span class="p">.</span><span class="nx">FunctionComponentElement</span><span class="o"><</span><span class="nx">React</span><span class="p">.</span><span class="nx">SVGProps</span><span class="o"><</span><span class="nx">SVGSVGElement</span><span class="o">>></span> <span class="o">=></span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="nx">node</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&&</span> <span class="k">typeof</span> <span class="nx">node</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span> <span class="o">&&</span> <span class="dl">"</span><span class="s2">type</span><span class="dl">"</span> <span class="k">in</span> <span class="nx">node</span> <span class="o">&&</span> <span class="k">typeof</span> <span class="nx">node</span><span class="p">.</span><span class="kd">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">function</span><span class="dl">"</span> <span class="p">);</span> <span class="p">};</span> <span class="cm">/** * Component to control the internal nodes of an SVG image. */</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">SvgController</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">FC</span><span class="o"><</span><span class="p">{</span> <span class="na">rules</span><span class="p">:</span> <span class="nx">ControlRules</span><span class="p">;</span> <span class="nl">children</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactElement</span><span class="o"><</span><span class="nx">ExtendedSVGProps</span><span class="o">></span><span class="p">;</span> <span class="p">}</span><span class="o">></span> <span class="o">=</span> <span class="p">({</span> <span class="nx">rules</span><span class="p">,</span> <span class="nx">children</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isFunctionalComponent</span><span class="p">(</span><span class="nx">children</span><span class="p">))</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">children</span><span class="p">;</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">SvgComponent</span> <span class="o">=</span> <span class="nx">children</span><span class="p">.</span><span class="kd">type</span><span class="p">({});</span> <span class="kd">const</span> <span class="nx">propsSelectorIndex</span> <span class="o">=</span> <span class="nx">getPropsSelectorIndex</span><span class="p">(</span><span class="nx">rules</span><span class="p">);</span> <span class="k">return</span> <span class="nx">React</span><span class="p">.</span><span class="nx">isValidElement</span><span class="o"><</span><span class="nx">ExtendedSVGProps</span><span class="o">></span><span class="p">(</span><span class="nx">SvgComponent</span><span class="p">)</span> <span class="p">?</span> <span class="nx">cloneNode</span><span class="p">(</span><span class="nx">SvgComponent</span><span class="p">,</span> <span class="nx">propsSelectorIndex</span><span class="p">)</span> <span class="p">:</span> <span class="nx">children</span><span class="p">;</span> <span class="p">};</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>What this component does is that it deconstructs the child React element provided by SVGR Webpack and then scans for any matching <code>rules</code> to inject the desired props. The main benefit of this approach is the ability to dynamically specify the injected color instead of predefining them in a style sheet. However, there may be a drawback in terms of performance. Although I haven’t benchmarked this function, it is possible that heavy usage could lead to performance issues.</p> <p>Now, let’s see it in action! First, create a new directory called <code>circles-helper-function</code> where we can add a new SVG image named <code>CirclesHelperFunction.svg</code>. Use the same SVG markup as the one used by the CSS module (<code>CirclesCssModule.svg</code>).</p> <p>Next, let’s create a new <code>page.tsx</code> file to demonstrate how the helper function can be utilized:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="dl">"</span><span class="s2">use client</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">useEffect</span><span class="p">,</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">CirclesHelperFunction</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./CirclesHelperFunction.svg</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">SvgController</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@/src/SvgController</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">getRandomColor</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="dl">"</span><span class="s2">#</span><span class="dl">"</span> <span class="o">+</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">16777215</span><span class="p">).</span><span class="nx">toString</span><span class="p">(</span><span class="mi">16</span><span class="p">);</span> <span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">CirclesHelperFunctionPage</span><span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">topLeftCircleColor</span><span class="p">,</span> <span class="nx">setTopLeftCircleColor</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">topRightCircleColor</span><span class="p">,</span> <span class="nx">setTopRightCircleColor</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">bottomLeftCircleColor</span><span class="p">,</span> <span class="nx">setBottomLeftCircleColor</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">);</span> <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">interval</span> <span class="o">=</span> <span class="nx">setInterval</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">setTopLeftCircleColor</span><span class="p">(</span><span class="nx">getRandomColor</span><span class="p">());</span> <span class="nx">setTopRightCircleColor</span><span class="p">(</span><span class="nx">getRandomColor</span><span class="p">());</span> <span class="nx">setBottomLeftCircleColor</span><span class="p">(</span><span class="nx">getRandomColor</span><span class="p">());</span> <span class="p">},</span> <span class="mi">100</span><span class="p">);</span> <span class="k">return</span> <span class="p">()</span> <span class="o">=></span> <span class="nx">clearInterval</span><span class="p">(</span><span class="nx">interval</span><span class="p">);</span> <span class="p">},</span> <span class="p">[]);</span> <span class="k">return</span> <span class="p">(</span> <span class="p"><></span> <span class="p"><</span><span class="nc">SvgController</span> <span class="na">rules</span><span class="p">=</span><span class="si">{</span><span class="p">[</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="p">{</span> <span class="na">attributeName</span><span class="p">:</span> <span class="dl">"</span><span class="s2">data-node-id</span><span class="dl">"</span><span class="p">,</span> <span class="na">attributeValue</span><span class="p">:</span> <span class="dl">"</span><span class="s2">top-left-circle</span><span class="dl">"</span><span class="p">,</span> <span class="p">},</span> <span class="na">props</span><span class="p">:</span> <span class="p">{</span> <span class="na">fill</span><span class="p">:</span> <span class="nx">topLeftCircleColor</span> <span class="p">},</span> <span class="p">},</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="p">{</span> <span class="na">attributeName</span><span class="p">:</span> <span class="dl">"</span><span class="s2">data-node-id</span><span class="dl">"</span><span class="p">,</span> <span class="na">attributeValue</span><span class="p">:</span> <span class="dl">"</span><span class="s2">top-right-circle</span><span class="dl">"</span><span class="p">,</span> <span class="p">},</span> <span class="na">props</span><span class="p">:</span> <span class="p">{</span> <span class="na">fill</span><span class="p">:</span> <span class="nx">topRightCircleColor</span> <span class="p">},</span> <span class="p">},</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="p">{</span> <span class="na">attributeName</span><span class="p">:</span> <span class="dl">"</span><span class="s2">data-node-id</span><span class="dl">"</span><span class="p">,</span> <span class="na">attributeValue</span><span class="p">:</span> <span class="dl">"</span><span class="s2">bottom-left-circle</span><span class="dl">"</span><span class="p">,</span> <span class="p">},</span> <span class="na">props</span><span class="p">:</span> <span class="p">{</span> <span class="na">fill</span><span class="p">:</span> <span class="nx">bottomLeftCircleColor</span> <span class="p">},</span> <span class="p">},</span> <span class="p">]</span><span class="si">}</span> <span class="p">></span> <span class="p"><</span><span class="nc">CirclesHelperFunction</span> <span class="p">/></span> <span class="p"></</span><span class="nc">SvgController</span><span class="p">></span> <span class="p"></></span> <span class="p">);</span> <span class="p">}</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>Because the color of each circle is tracked as a state, every time <code>useEffect</code> changes the color, the <code>SvgController</code> component is updated, which then propagates those changes to the <code>CirclesHelperFunction</code> component.</p> <p>This approach allows you to use a standard SVG file and control all of its nodes using standard React code.</p> <h2> <a name="using-the-components-provided-in-this-article" href="#using-the-components-provided-in-this-article"> </a> Using the components provided in this article </h2> <p>While we’ve provided useful components in this article, it’s important to understand that they should be used with caution.</p> <p>The main challenge I faced when creating these components was due to the SVGR Webpack loader returning a functional component that doesn’t offer the same extended capabilities as the original component. This means that, to add these capabilities, we have to use APIs like <code>React.cloneElement</code>, which is <a href="https://react.dev/reference/react/cloneElement">not recommended</a> by React. I even had to go a step further and invoke the functional component using <code>.type()</code>, an internal React API not documented in the official React resources.</p> <p>The “proper” approach to creating these components would likely involve writing a wrapper for the SVGR webpack loader, enabling the same manipulations directly from the imported component.</p> <p>That being said, while generally it is advised against using <code>cloneElement</code> or internal APIs, the risk is relatively low in this specific use case given the widespread usage of React.</p> <p>If you prefer to err on the side of caution, all the other examples presented without using these components are safe to use and already offer sufficient flexibility for most common use cases.</p> <h2> <a name="conclusion" href="#conclusion"> </a> Conclusion </h2> <p>In conclusion, the information shared in this article aims to empower you to fully utilize the potential of SVG images. SVGs are incredibly useful for design purposes, especially when combined with popular UI frameworks and tools. This combination allows you to create visually appealing and interactive web experiences. While the focus of this article is mainly on Next.js and React, you can achieve similar results using other technologies by following similar approaches. Ultimately, these techniques work within HTML documents and web browsers, making them universally applicable. So go ahead and dive into using SVGs in your projects using these new tricks, and experience the incredible results they can offer.</p>

  • Visual Studio Code

    Visual Studio Code

  • In this example, we’ve added id properties to all circles, allowing us to target them individually within the tag. I’ve defined two “color modes” called oceanBreeze and sunsetGlow. You can set the color mode by adding a data-color-mode property to the element. To preview the SVG and see the live result, you can install an SVG preview plugin for VSCode or use other similar tools. For example, to view the SVG with the oceanBreeze color mode, modify the code as follows:

     viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" data-color-mode="oceanBreeze">
        
    
    
    Enter fullscreen mode Exit fullscreen mode

    ocean breeze dots

    To control the color mode using React, let’s create a new page.tsx in a directory named circles-inline-style:

    import CirclesInlineStyle from "./CirclesInlineStyle.svg";
    
    export default function CirclesInlineStylePage() {
      return <CirclesInlineStyle data-color-mode="sunsetGlow" />;
    }
    
    Enter fullscreen mode Exit fullscreen mode

    The resulting SVG will display the circles with the expected colors:

    sunset glow dots

    ⚠ Be cautious when using inline styles, as different browsers may produce varying results. Make sure to test carefully. Additionally, setting styles on elements like or using advanced CSS may lead to cross-browser issues. Sticking to basic styles will help avoid these problems.

    2) Using Next.js’ built-in CSS modules

    To implement this approach, let’s create a new page in a directory called circles-css-module. Within this directory, we’ll create a new CSS module file named circles.module.css:

    /** Ocean breeze */
    .oceanBreeze [data-node-id="top-right-circle"] {
      fill: DeepSkyBlue;
    }
    
    .oceanBreeze [data-node-id="top-left-circle"] {
      fill: MediumAquaMarine;
    }
    
    .oceanBreeze [data-node-id="bottom-right-circle"] {
      fill: CornflowerBlue;
    }
    
    /** Sunset glow */
    .sunsetGlow [data-node-id="top-right-circle"] {
      fill: Coral;
    }
    
    .sunsetGlow [data-node-id="top-left-circle"] {
      fill: Gold;
    }
    
    .sunsetGlow [data-node-id="bottom-right-circle"] {
      fill: DarkOrange;
    }
    
    Enter fullscreen mode Exit fullscreen mode

    In this CSS module file, we define different selectors for the color modes “oceanBreeze” and “sunsetGlow”. Instead of using the id selector, we use attribute selectors with data-node-id. This is because the SVGR loader removes any id attributes from elements when inline styles are not used, to avoid collisions with the parent HTML document.

    Next, let’s create a new SVG file named CirclesCssModule.svg in the same directory. This SVG file will be identical to the one used in the inline style approach but without the </code> tag. Also, we’ll use <code>data-node-id</code> attributes instead of <code>id</code> to identify the circles:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight xml"><code><span class="nt"><svg</span> <span class="na">viewBox=</span><span class="s">"0 0 200 200"</span> <span class="na">xmlns=</span><span class="s">"http://www.w3.org/2000/svg"</span><span class="nt">></span> <span class="nt"><g</span> <span class="na">transform=</span><span class="s">"matrix(1,0,0,1,0,0)"</span><span class="nt">></span> <span class="c"><!-- Top-left circle --></span> <span class="nt"><circle</span> <span class="na">data-node-id=</span><span class="s">"top-left-circle"</span> <span class="na">fill=</span><span class="s">"black"</span> <span class="na">cx=</span><span class="s">"50"</span> <span class="na">cy=</span><span class="s">"50"</span> <span class="na">r=</span><span class="s">"40"</span> <span class="nt">/></span> <span class="c"><!-- Top-right circle --></span> <span class="nt"><circle</span> <span class="na">data-node-id=</span><span class="s">"top-right-circle"</span> <span class="na">fill=</span><span class="s">"black"</span> <span class="na">cx=</span><span class="s">"150"</span> <span class="na">cy=</span><span class="s">"40"</span> <span class="na">r=</span><span class="s">"30"</span> <span class="nt">/></span> <span class="c"><!-- Bottom-right circle --></span> <span class="nt"><circle</span> <span class="na">data-node-id=</span><span class="s">"bottom-right-circle"</span> <span class="na">fill=</span><span class="s">"black"</span> <span class="na">cx=</span><span class="s">"150"</span> <span class="na">cy=</span><span class="s">"150"</span> <span class="na">r=</span><span class="s">"20"</span> <span class="nt">/></span> <span class="nt"></g></span> <span class="nt"></svg></span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>Now, let’s create a new <code>page.tsx</code> file where we can import the SVG component and apply the CSS module:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="nx">CirclesCssModule</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./CirclesCssModule.svg</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">styles</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./circles.module.css</span><span class="dl">"</span><span class="p">;</span> <span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">CirclesCssModulePage</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p"><</span><span class="nc">CirclesCssModule</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">styles</span><span class="p">.</span><span class="nx">sunsetGlow</span><span class="si">}</span> <span class="p">/>;</span> <span class="p">}</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>In this example, we import the <code>CirclesCssModule</code> component from the <code>CirclesCssModule.svg</code> file. We also import the generated CSS module file <code>styles</code> that contain the class names for the color modes.</p> <p>By applying the <code>styles.sunsetGlow</code> class name as a prop to the <code>CirclesCssModule</code> component, we can dynamically set the color mode.</p> <p>The resulting SVG will appear the same as the inline style approach but with the advantage of being able to separate the CSS into a style sheet. This means you can utilize additional features like <a href="https://nextjs.org/docs/app/building-your-application/styling/sass">Sass</a>, which is not natively supported within SVG files.</p> <p>However, one drawback of this approach is that you can no longer preview the styles using standard preview tools since React is required to apply the styles dynamically.</p> <h4> <a name="3-using-a-helper-function" href="#3-using-a-helper-function"> </a> 3) Using a helper function </h4> <p>If predefining styles isn’t an option for you and you’re seeking greater flexibility, using a wrapping component may be your only remaining option. We’ve already created such a component to demonstrate how this can be achieved. However, please note that due to the nature of SVGR imports, this is not entry-level code. You can create the helper function under the <code>/src</code> directory and name it <code>SvgController.ts</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="kd">type</span> <span class="nx">ExtendedSVGProps</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">SVGProps</span><span class="o"><</span><span class="nx">SVGSVGElement</span><span class="o">></span> <span class="o">&</span> <span class="p">{</span> <span class="p">[</span><span class="na">attr</span><span class="p">:</span> <span class="kr">string</span><span class="p">]:</span> <span class="kr">string</span><span class="p">;</span> <span class="p">};</span> <span class="kd">type</span> <span class="nx">ControlRule</span> <span class="o">=</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="p">{</span> <span class="na">attributeName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">attributeValue</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="p">};</span> <span class="nl">props</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">SVGProps</span><span class="o"><</span><span class="nx">SVGSVGElement</span><span class="o">></span><span class="p">;</span> <span class="p">};</span> <span class="kd">type</span> <span class="nx">ControlRules</span> <span class="o">=</span> <span class="nx">ControlRule</span><span class="p">[];</span> <span class="cm">/** * Get an object that indexes control rules. * * @param rules - The control rules. * * @returns An object that indexes control rules. */</span> <span class="kd">const</span> <span class="nx">getPropsSelectorIndex</span> <span class="o">=</span> <span class="p">(</span> <span class="nx">rules</span><span class="p">:</span> <span class="nx">ControlRules</span> <span class="p">):</span> <span class="p">{</span> <span class="p">[</span><span class="nx">key</span><span class="p">:</span> <span class="kr">string</span><span class="p">]:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">SVGProps</span><span class="o"><</span><span class="nx">SVGSVGElement</span><span class="o">></span> <span class="p">}</span> <span class="o">=></span> <span class="p">{</span> <span class="k">return</span> <span class="nx">rules</span><span class="p">.</span><span class="nx">reduce</span><span class="p">((</span><span class="nx">acc</span><span class="p">,</span> <span class="nx">config</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="nx">attributeName</span><span class="p">,</span> <span class="nx">attributeValue</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">config</span><span class="p">.</span><span class="nx">selector</span><span class="p">;</span> <span class="nx">acc</span><span class="p">[</span><span class="nx">attributeName</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">:</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">attributeValue</span><span class="p">]</span> <span class="o">=</span> <span class="nx">config</span><span class="p">.</span><span class="nx">props</span><span class="p">;</span> <span class="k">return</span> <span class="nx">acc</span><span class="p">;</span> <span class="p">},</span> <span class="p">{}</span> <span class="k">as</span> <span class="p">{</span> <span class="p">[</span><span class="na">key</span><span class="p">:</span> <span class="kr">string</span><span class="p">]:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">SVGProps</span><span class="o"><</span><span class="nx">SVGSVGElement</span><span class="o">></span> <span class="p">});</span> <span class="p">};</span> <span class="cm">/** * Clone a React node and its children while trying to inject new props. * * @param node - The node to clone. * @param propsSelectorIndex - An object that indexes control rules. * * @returns The cloned node with new props when applicable. */</span> <span class="kd">const</span> <span class="nx">cloneNode</span> <span class="o">=</span> <span class="p">(</span> <span class="nx">node</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactElement</span><span class="o"><</span><span class="nx">ExtendedSVGProps</span><span class="o">></span><span class="p">,</span> <span class="nx">propsSelectorIndex</span><span class="p">:</span> <span class="p">{</span> <span class="p">[</span><span class="nx">key</span><span class="p">:</span> <span class="kr">string</span><span class="p">]:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">SVGProps</span><span class="o"><</span><span class="nx">SVGSVGElement</span><span class="o">></span> <span class="p">}</span> <span class="p">):</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactElement</span><span class="o"><</span><span class="nx">ExtendedSVGProps</span><span class="o">></span> <span class="o">=></span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="nx">children</span><span class="p">,</span> <span class="p">...</span><span class="nx">restProps</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">node</span><span class="p">.</span><span class="nx">props</span><span class="p">;</span> <span class="kd">let</span> <span class="na">nodeProps</span><span class="p">:</span> <span class="nb">Partial</span><span class="o"><</span><span class="nx">ExtendedSVGProps</span><span class="o">></span> <span class="o">&</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Attributes</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">restProps</span><span class="p">,</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">matchingProps</span> <span class="o">=</span> <span class="nx">propsSelectorIndex</span><span class="p">[</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">nodeProps</span><span class="p">).</span><span class="nx">find</span><span class="p">(</span> <span class="p">([</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">])</span> <span class="o">=></span> <span class="nx">propsSelectorIndex</span><span class="p">[</span><span class="nx">key</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">:</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">value</span><span class="p">]</span> <span class="p">)?.[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">:</span><span class="dl">"</span> <span class="o">+</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">nodeProps</span><span class="p">).</span><span class="nx">find</span><span class="p">(</span> <span class="p">([</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">])</span> <span class="o">=></span> <span class="nx">propsSelectorIndex</span><span class="p">[</span><span class="nx">key</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">:</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">value</span><span class="p">]</span> <span class="p">)?.[</span><span class="mi">1</span><span class="p">]</span> <span class="p">];</span> <span class="k">if</span> <span class="p">(</span><span class="nx">matchingProps</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">compatibleProps</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">matchingProps</span><span class="p">).</span><span class="nx">reduce</span><span class="p">(</span> <span class="p">(</span><span class="nx">acc</span><span class="p">,</span> <span class="p">[</span><span class="nx">propKey</span><span class="p">,</span> <span class="nx">propValue</span><span class="p">])</span> <span class="o">=></span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">propValue</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span> <span class="nx">acc</span><span class="p">[</span><span class="nx">propKey</span><span class="p">]</span> <span class="o">=</span> <span class="nx">propValue</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">acc</span><span class="p">;</span> <span class="p">},</span> <span class="p">{}</span> <span class="k">as</span> <span class="nx">ExtendedSVGProps</span> <span class="p">);</span> <span class="nx">nodeProps</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">nodeProps</span><span class="p">,</span> <span class="p">...</span><span class="nx">compatibleProps</span> <span class="p">};</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">clonedChildren</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Children</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">children</span><span class="p">,</span> <span class="p">(</span><span class="nx">child</span><span class="p">)</span> <span class="o">=></span> <span class="nx">React</span><span class="p">.</span><span class="nx">isValidElement</span><span class="o"><</span><span class="nx">ExtendedSVGProps</span><span class="o">></span><span class="p">(</span><span class="nx">child</span><span class="p">)</span> <span class="p">?</span> <span class="nx">cloneNode</span><span class="p">(</span><span class="nx">child</span><span class="p">,</span> <span class="nx">propsSelectorIndex</span><span class="p">)</span> <span class="p">:</span> <span class="nx">child</span> <span class="p">);</span> <span class="k">return</span> <span class="nx">React</span><span class="p">.</span><span class="nx">cloneElement</span><span class="p">(</span><span class="nx">node</span><span class="p">,</span> <span class="nx">nodeProps</span><span class="p">,</span> <span class="nx">clonedChildren</span><span class="p">);</span> <span class="p">};</span> <span class="cm">/** * Type guard to check if a React node is a functional component. * * @param node - A React node to check. * * @returns True if it's a functional component, otherwise false. */</span> <span class="kd">const</span> <span class="nx">isFunctionalComponent</span> <span class="o">=</span> <span class="p">(</span> <span class="nx">node</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactNode</span> <span class="p">):</span> <span class="nx">node</span> <span class="k">is</span> <span class="nx">React</span><span class="p">.</span><span class="nx">FunctionComponentElement</span><span class="o"><</span><span class="nx">React</span><span class="p">.</span><span class="nx">SVGProps</span><span class="o"><</span><span class="nx">SVGSVGElement</span><span class="o">>></span> <span class="o">=></span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="nx">node</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&&</span> <span class="k">typeof</span> <span class="nx">node</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span> <span class="o">&&</span> <span class="dl">"</span><span class="s2">type</span><span class="dl">"</span> <span class="k">in</span> <span class="nx">node</span> <span class="o">&&</span> <span class="k">typeof</span> <span class="nx">node</span><span class="p">.</span><span class="kd">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">function</span><span class="dl">"</span> <span class="p">);</span> <span class="p">};</span> <span class="cm">/** * Component to control the internal nodes of an SVG image. */</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">SvgController</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">FC</span><span class="o"><</span><span class="p">{</span> <span class="na">rules</span><span class="p">:</span> <span class="nx">ControlRules</span><span class="p">;</span> <span class="nl">children</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactElement</span><span class="o"><</span><span class="nx">ExtendedSVGProps</span><span class="o">></span><span class="p">;</span> <span class="p">}</span><span class="o">></span> <span class="o">=</span> <span class="p">({</span> <span class="nx">rules</span><span class="p">,</span> <span class="nx">children</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isFunctionalComponent</span><span class="p">(</span><span class="nx">children</span><span class="p">))</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">children</span><span class="p">;</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">SvgComponent</span> <span class="o">=</span> <span class="nx">children</span><span class="p">.</span><span class="kd">type</span><span class="p">({});</span> <span class="kd">const</span> <span class="nx">propsSelectorIndex</span> <span class="o">=</span> <span class="nx">getPropsSelectorIndex</span><span class="p">(</span><span class="nx">rules</span><span class="p">);</span> <span class="k">return</span> <span class="nx">React</span><span class="p">.</span><span class="nx">isValidElement</span><span class="o"><</span><span class="nx">ExtendedSVGProps</span><span class="o">></span><span class="p">(</span><span class="nx">SvgComponent</span><span class="p">)</span> <span class="p">?</span> <span class="nx">cloneNode</span><span class="p">(</span><span class="nx">SvgComponent</span><span class="p">,</span> <span class="nx">propsSelectorIndex</span><span class="p">)</span> <span class="p">:</span> <span class="nx">children</span><span class="p">;</span> <span class="p">};</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>What this component does is that it deconstructs the child React element provided by SVGR Webpack and then scans for any matching <code>rules</code> to inject the desired props. The main benefit of this approach is the ability to dynamically specify the injected color instead of predefining them in a style sheet. However, there may be a drawback in terms of performance. Although I haven’t benchmarked this function, it is possible that heavy usage could lead to performance issues.</p> <p>Now, let’s see it in action! First, create a new directory called <code>circles-helper-function</code> where we can add a new SVG image named <code>CirclesHelperFunction.svg</code>. Use the same SVG markup as the one used by the CSS module (<code>CirclesCssModule.svg</code>).</p> <p>Next, let’s create a new <code>page.tsx</code> file to demonstrate how the helper function can be utilized:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="dl">"</span><span class="s2">use client</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">useEffect</span><span class="p">,</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">CirclesHelperFunction</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./CirclesHelperFunction.svg</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">SvgController</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@/src/SvgController</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">getRandomColor</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="dl">"</span><span class="s2">#</span><span class="dl">"</span> <span class="o">+</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">16777215</span><span class="p">).</span><span class="nx">toString</span><span class="p">(</span><span class="mi">16</span><span class="p">);</span> <span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">CirclesHelperFunctionPage</span><span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">topLeftCircleColor</span><span class="p">,</span> <span class="nx">setTopLeftCircleColor</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">topRightCircleColor</span><span class="p">,</span> <span class="nx">setTopRightCircleColor</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">bottomLeftCircleColor</span><span class="p">,</span> <span class="nx">setBottomLeftCircleColor</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">);</span> <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">interval</span> <span class="o">=</span> <span class="nx">setInterval</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">setTopLeftCircleColor</span><span class="p">(</span><span class="nx">getRandomColor</span><span class="p">());</span> <span class="nx">setTopRightCircleColor</span><span class="p">(</span><span class="nx">getRandomColor</span><span class="p">());</span> <span class="nx">setBottomLeftCircleColor</span><span class="p">(</span><span class="nx">getRandomColor</span><span class="p">());</span> <span class="p">},</span> <span class="mi">100</span><span class="p">);</span> <span class="k">return</span> <span class="p">()</span> <span class="o">=></span> <span class="nx">clearInterval</span><span class="p">(</span><span class="nx">interval</span><span class="p">);</span> <span class="p">},</span> <span class="p">[]);</span> <span class="k">return</span> <span class="p">(</span> <span class="p"><></span> <span class="p"><</span><span class="nc">SvgController</span> <span class="na">rules</span><span class="p">=</span><span class="si">{</span><span class="p">[</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="p">{</span> <span class="na">attributeName</span><span class="p">:</span> <span class="dl">"</span><span class="s2">data-node-id</span><span class="dl">"</span><span class="p">,</span> <span class="na">attributeValue</span><span class="p">:</span> <span class="dl">"</span><span class="s2">top-left-circle</span><span class="dl">"</span><span class="p">,</span> <span class="p">},</span> <span class="na">props</span><span class="p">:</span> <span class="p">{</span> <span class="na">fill</span><span class="p">:</span> <span class="nx">topLeftCircleColor</span> <span class="p">},</span> <span class="p">},</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="p">{</span> <span class="na">attributeName</span><span class="p">:</span> <span class="dl">"</span><span class="s2">data-node-id</span><span class="dl">"</span><span class="p">,</span> <span class="na">attributeValue</span><span class="p">:</span> <span class="dl">"</span><span class="s2">top-right-circle</span><span class="dl">"</span><span class="p">,</span> <span class="p">},</span> <span class="na">props</span><span class="p">:</span> <span class="p">{</span> <span class="na">fill</span><span class="p">:</span> <span class="nx">topRightCircleColor</span> <span class="p">},</span> <span class="p">},</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="p">{</span> <span class="na">attributeName</span><span class="p">:</span> <span class="dl">"</span><span class="s2">data-node-id</span><span class="dl">"</span><span class="p">,</span> <span class="na">attributeValue</span><span class="p">:</span> <span class="dl">"</span><span class="s2">bottom-left-circle</span><span class="dl">"</span><span class="p">,</span> <span class="p">},</span> <span class="na">props</span><span class="p">:</span> <span class="p">{</span> <span class="na">fill</span><span class="p">:</span> <span class="nx">bottomLeftCircleColor</span> <span class="p">},</span> <span class="p">},</span> <span class="p">]</span><span class="si">}</span> <span class="p">></span> <span class="p"><</span><span class="nc">CirclesHelperFunction</span> <span class="p">/></span> <span class="p"></</span><span class="nc">SvgController</span><span class="p">></span> <span class="p"></></span> <span class="p">);</span> <span class="p">}</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>Because the color of each circle is tracked as a state, every time <code>useEffect</code> changes the color, the <code>SvgController</code> component is updated, which then propagates those changes to the <code>CirclesHelperFunction</code> component.</p> <p>This approach allows you to use a standard SVG file and control all of its nodes using standard React code.</p> <h2> <a name="using-the-components-provided-in-this-article" href="#using-the-components-provided-in-this-article"> </a> Using the components provided in this article </h2> <p>While we’ve provided useful components in this article, it’s important to understand that they should be used with caution.</p> <p>The main challenge I faced when creating these components was due to the SVGR Webpack loader returning a functional component that doesn’t offer the same extended capabilities as the original component. This means that, to add these capabilities, we have to use APIs like <code>React.cloneElement</code>, which is <a href="https://react.dev/reference/react/cloneElement">not recommended</a> by React. I even had to go a step further and invoke the functional component using <code>.type()</code>, an internal React API not documented in the official React resources.</p> <p>The “proper” approach to creating these components would likely involve writing a wrapper for the SVGR webpack loader, enabling the same manipulations directly from the imported component.</p> <p>That being said, while generally it is advised against using <code>cloneElement</code> or internal APIs, the risk is relatively low in this specific use case given the widespread usage of React.</p> <p>If you prefer to err on the side of caution, all the other examples presented without using these components are safe to use and already offer sufficient flexibility for most common use cases.</p> <h2> <a name="conclusion" href="#conclusion"> </a> Conclusion </h2> <p>In conclusion, the information shared in this article aims to empower you to fully utilize the potential of SVG images. SVGs are incredibly useful for design purposes, especially when combined with popular UI frameworks and tools. This combination allows you to create visually appealing and interactive web experiences. While the focus of this article is mainly on Next.js and React, you can achieve similar results using other technologies by following similar approaches. Ultimately, these techniques work within HTML documents and web browsers, making them universally applicable. So go ahead and dive into using SVGs in your projects using these new tricks, and experience the incredible results they can offer.</p>

NOTE: The number of mentions on this list indicates mentions on common posts plus user suggested alternatives. Hence, a higher number means a more popular project.

Suggest a related project

Related posts