Topics
Understanding SVG icons
Scalable Vector Graphics (SVG) is an image format built in the XML markup language that has several advantages over other image file formats, particularly when used for icons, logos, and graphics. Unlike normal image formats (jpeg, gif, etc.), which are pixel-based, SVGs can scale up or down at any resolution using mathematically defined vectors. You can further explore SVGs in the W3C related page.
Here is a simple SVG icon example exported using streamline:
<svg xmlns="http://www.w3.org/2000/svg " viewBox="0 0 2500 2500" height="24" width="24">
<path d="M13,6.81l-5.95,6a2.48,2.48,0,0,..."
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round">
</path>
</svg>
Structure of SVG Icons
We'll be working with small SVG icons like the one above in this article. These icons are frequently used in buttons, notifications, navigation menus, etc., either in place of text or to completely replace text.
When creating these SVGs, it's a good idea to keep the XML format consistent across all icons of our app so that we can manipulate them all in the same way. If we use the evenodd
algorithm or outline-stroke
option described in the streamline SVG export tool, the XML structure for these SVG icons will be easier to handle.
In order to preserve all the necessary data, this option will aggregate all the inner vectors of the SVG icon into a path element using additional dimensions.
Although we desire access to the inner vectors of the SVG picture, this technique might not be optimal for huge SVG graphics or SVG that are used in animations.
// using evenodd algorithm or outline-stroke option of strealine
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" version="1.1">
<path d="M 1664.500 0.579 C 1662.850 0.800, 1655.875
1.483, 1649 2.096 C 1595.351 6.882, 1536.713 ..."
stroke="none" fill="currentColor"
fill-rule="evenodd"></path>
</svg>
You may have also noticed that the SVG tag in the example above lacks width and height attributes. When working with SVG images, we don't have to set the width and height of the icon directly. Instead, we can use the viewBox attribute to tell the image where and how it should scale. It also helps keep problems from happening with how the width and height attributes work in different browsers. For instance, while most browsers will scale the image as expected if we specify width="100"
and height="auto"
, Internet Explorer will scale the image differently.
SVG Accessibility
There are several methods for rendering SVG icons in React. There is also a simpler method to completely bypass React by directly adding the SVG's XML code to an HTML file or the JSX code.
However, this method is not optimal, especially when we are dealing with a large number of SVGs in our HTML or JSX. A possible way to address this issue is to save the SVG files locally or online and render them as images using HTML img tags.
<img src="path/to/svg_image.svg"
alt="This is my image alt text"
title="My image title" />
This will also improve the site's Search Engine Optimization (SEO), since we have a simpler HTML file in our browser for the search engine crawlers to read. Another important thing to remember is that Google Image Search won't show us any inlined SVG images.
Choosing the difficult route for scalability
While the above solves the SEO issues and the existence of large XML files in the browser's HTML, it makes our SVG icons less flexible and less scalable. For instance, we cannot directly change the fill colour of an SVG icon when it is rendered through an img tag. We will have to resort to using alternatives such as filter or mask in CSS, which are difficult to handle and have limited browser support.
As a result, we may render SVG icons directly in HTML, however we have to use some additional aria attributes and XML elements to make them invisible to screen readers if they are decorative icons or to display the appropriate replacement text if they are crucial to the application's user experience.
- Adding aria-hidden="true" to remove the icon from the accessibility tree
// aria-hidden="true" is added on svg tag
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" version="1.1">
<path d="M 1664.500 0.579 C 1662.850 0.800, 1655.875
1.483, 1649 2.096 C 1595.351 6.882, 1536.713 ..."
stroke="none" fill="currentColor"
fill-rule="evenodd"></path>
</svg>
- Adding role="img" and title text to meaningful icons. The title's text should reflect the icon's behaviour.
// role="img" and aria-labelledby="attachment-svg" is added on svg tag to
// indicate the descriptive title.
<svg role="img" aria-labelledby="attachment-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" version="1.1">
<title id="attachment-svg">Shows the presence of a file attachment</title>
<path d="M 1664.500 0.579 C 1662.850 0.800, 1655.875
1.483, 1649 2.096 C 1595.351 6.882, 1536.713 ..."
stroke="none" fill="currentColor"
fill-rule="evenodd"></path>
</svg>
Check out this excellent article on accessible SVG and preserving support for older browser versions.
In order to make our example simpler, we will use SVG by removing them from the accessibility tree (using aria-role="true").
Rendering SVG Icons Through a React Component - Step by Step Guide
Disclaimer 1: It should be noted that the usage of the React library for SVG handling is merely indicative; we can use any other framework or library, such as Angular, Vue, Svelte, or vanilla JavaScript/Typescript, instead.
Disclaimer 2: You must have a basic understanding of Typescript and ReactJs in order to follow the upcoming step-by-step tutorial.
Now let's look at how to construct a React component that will manage every SVG icon in our application. Importing SVG icons into react components will be simple if we use CRA (create-react-app), as the created react project will take care of the import details inside. As an alternative, if we are using a custom bundler like webpack
, we will need to install the package@svgr/webpack
into our project and add it to the bundler's configuration.
// install the package
npm install @svgr/webpack --save-dev
// add it to webpack config
const webpack = require('webpack');
module.exports = {
entry: './src/index.js',
module: {
rules: [
{
test: /\.svg$/,
include: path.resolve(__dirname, 'src/icons'),
use: ['@svgr/webpack']
},
],
},
};
Export settings
After establishing all of the project-related configurations, we can use a react component to render our first image in the browser. We should import an icon into our project first. With the help of the streamline tool, we can create a new SVG icon (make sure the Outline-Strokes and Responsive options are checked).
Then copy the XML by clicking the copy button, and then create a new file attachment.svg
in the icons
dir and paste the copied content there.
Here is the example of the above process. We just render the SVG file as a React component by using the default SVG loader from create-react-app.
Rending the icons through a React component
Although the above process is straightforward and simple, as our application's codebase and the number of SVG icons we manage expand, we may run into problems. We will therefore have various SVG components scattered throughout the code of our application. This could lead to discrepancies in the SVGs' dimensions (width and height), colour, alt-text descriptions, loading times in the browser, and other factors.
Let's create the following configuration in icons/index.ts
file.
import { SVGAttributes, VFC } from "react";
export type IconName = "attachment" | "bomb" | "brush" | "clip" | "copy";
type IconConfig = { component: VFC<SVGAttributes<SVGElement>> };
export const svgIconsConfig: Record<IconName, IconConfig> = {
attachment: { component: AttachmentIcon },
bomb: { component: BombIcon },
brush: { component: BrushIcon },
clip: { component: ClipIcon },
copy: { component: CopyIcon }
};
You may verify that we have a key-value mapping in the configuration that has been built. Every icon component has a specific key that corresponds to the icon. Another thing to note is that this setup is adaptable and extensible since we can add more properties according to the behaviour of each icon.
After we specified our icons configuration we can create a React component that will use the configuration in order to render the right icon to the dom. The configuration and usage of typescript give us the ability to have typed properties in our Icon component and avoid issues when using the component by passing the wrong properties values.
Adding size configuration
Let's take a step back now and review our configuration once more. We notice right away that our configuration is missing any icon dimensions (width, height).
export const svgIconsConfig: Record<IconName, IconConfig> = {
attachment: { component: AttachmentIcon },
...
}
Therefore, we should think about whether managing icon scaling for each separately is appropriate or if we should handle sizing for all icons through a different setting. Size is a crucial property for img tags and icons, thus we want to be able to directly adjust it.
const BASE_SIZE = 16;
const sizeConfig = {
sm: {
w: BASE_SIZE,
h: BASE_SIZE,
},
md: {
w: BASE_SIZE * 2,
h: BASE_SIZE * 2,
},
xl: {
w: BASE_SIZE * 3,
h: BASE_SIZE * 3,
},
};
In order to prevent our icons from being rendered with incorrect dimensions, we can position our size configuration at the component level rather than hiding it in the icons configuration.
Adding the colour configuration
Lastly, we will enhance the Icon component with a colour property. This enhancement may be challenging because it demands for a consistent XML structure across all of our SVG icons that are rendered by the Icon component.
Let's check the following image,
If the SVG does not adhere to the guidelines for theevenodd algorithm that we specified at the beginning, we will experience inconsistencies in the XML structure of our SVGs and be unable to properly colour them using a single component, or we will need to increase the complexity of the Icon component.
So, handling all SVG icons through a single component and encapsulating the configuration for these icons in a separate module will be a good way to address the concerns highlighted. We will be able to enhance the functionality of all the icons simultaneously through a single source of truth, manage many icons, provide safeguards for appropriately handling those icons, and dynamically change the colour, size, or name of each icon.
Some things to consider when handling SVGs through a single react component
In some circumstances, each SVG will serve a separate purpose in the app, such as handling different graphic animations, background visuals, or a very vast and sophisticated XML structure.
Handling SVG through a single source of truth (configuration & component) is not advised in these circumstances because we will need to handle each image individually.
Conclusion
SVG icon handling in React can be hard, resulting in inconsistencies and code redundancy that will eventually damage the application's performance, scalability, and development experience.
So, if we use a lot of small icons in our app and want them to behave similarly, handling them through a centralised configuration and single source of truth component will help us organise our codebase, improve the overall performance of our icon rendering, and make it scalable for future expansion.
Nevertheless we shouldn't forget that maintaining consistency in our SVG icons XML structure and keeping it as lean as possible utilizing efficient algorithms likeevenodd is the most crucial aspect of managing SVG through a single component.
Note: This post is not supported by any organisation or product and is based on my personal research and experience.
Useful links:
Top comments (0)