Working with SVGs in React

Importing SVGs with Webpack and animating them via CSS & Styled Components

Ross Bulat
Jul 1, 2019 · 11 min read

SVGs are now the standard means of illustration

This article will explore the standard tools used for importing SVGs in React projects today, before delving into some techniques for theming and animating SVGs with CSS and Styled Components.

SVGs are now widely supported within browsers, with their capabilities further enhanced with CSS styling to control state transitions and animation. Modern web apps of today rely on SVGs to aid in responsive app design, smaller file size and less HTTP requests, making a far greater user experience than using pixel-based images.

For the React framework, SVGs are now very well supported via a Webpack loader, where SVGs can be imported as components into any module and optimised at build time. Create React App have been using this loader since version 2, where importing SVGs and embedding them in your JSX is a breeze:

// importing a logo SVG and embedding it in JSXimport { ReactComponent as Logo } from './logo.svg';const MyComponent = () => {
return (
...
<Logo />
);
}

The package used here is called SVGR. It is the most widely adopted means of transforming SVGs into React components.

SVGR — SVG (React)

SVGR takes external SVG files and transforms them into React components. The package hosts a range of utilities, each of which offering solutions that depend on the context of your SVG manipulation.

The most widely adopted utility at this time is their Webpack loader, currently on 1.4 million weekly downloads, indicating this is easily the Javascript community’s favoured approach for importing SVGs into React.

The Webpack loader is one of three main solutions that SVGR offer, depending on whether you wish to manipulate SVGs on the command-line, in an automated script, or when you are importing them into modules:

  • @svgr/cli: The CLI tool, giving us simple commands to either convert single files or entire directories of SVG files into React component .js files
  • @svgr/core: This core SVGR package is designed to be embedded within NodeJS programs to construct automated tools for processing SVGs into components
  • @svgr/webpack: The most widely adopted solution, and an elegant means of importing SVGs as React components
  • As a Rollup or Parcel plugin: Two other Javascript module bundlers that are less know, but have support for SVGR.

There are advantages of using the top three packages hand-in-hand; the CLI, Node API and Webpack packages can be adopted within your SVG management workflow for React projects, if needed.

What we are solely focussed on here however is the Webpack loader, that we will use further down the article to demonstrate a real-world use case for modern SVG integration.

Manipulating SVGs via CSS

There are a number of ways we can use CSS in accordance with SVGs, but since we are React focused developers, we will be interested in a Styled Components implementation.

For completeness, lets quickly review the various ways CSS is used within SVGs, as software such as Illustrator will undoubtedly be exporting SVGs with at least some of these techniques. By understanding exactly how styles are assigned to SVGs, you will then be able to optimise and remove styles that are no longer necessary, or that are defined elsewhere in your project.

Inline attributes

Styles will very commonly be applied to the SVG XML itself, not dissimilar to the following:

// CSS properties via attributes<svg>
...
<path
fill="none"
stroke-linecap="round"
stroke-miterlimit="10"
stroke="#000"
stroke-width="15"
...
/>
</svg>

Note: Some of these properties will undoubtedly be defined at the component level of your projects too, but it is worth keeping some default values inside the SVG itself for easier previewing, such as stroke, fill, stroke-width and stroke-linecap.

The style attribute

Although less common, the style attribute is supported within an SVG too:

<path 
style="stroke: #000; ..."
...
/>

Inline stylesheets with the style tag

It is also common for SVG packages to use the <style /> tag with CSS classes for a range of paths and shapes. The <style /> tag is defined within a <defs /> tag:

<svg>
<defs>
<style>
.cls-1 {
fill: none;
stroke-linecap: round;
stroke-miterlimit: 10;
stroke-width:15px
}
</style>
</defs>
<path
class="shine cls-1"
...
/>
</svg>

Either style an entire shape, such as a circle, or create classes to be assigned to paths and shapes.

External stylesheets

We are getting closer to a more modular design pattern now, by separating the CSS from the SVG file and into a separate stylesheet. This makes for easier modification, and the stylesheet can either be included within your web page header, or in the SVG file itself:

<?xml-stylesheet type="text/css" href="svg-stylesheet.css" ?>
<svg>
<circle
cx="20"
cy="20"
r="10"
/>
</svg>

This latter approach is not ideal for a Javascript framework; we instead would ideally like styles to be defined on the component level with no external dependencies like CSS stylesheets. Therefore, let’s move on to using Styled Components in conjunction with SVGs to create some great looking animation, and coincide with app theming in the process.

Styled Components and SVGs

Being able to embed SVG CSS properties within React components is perhaps the most efficient means of styling an SVG via CSS, offering a range of benefits:

  • We can export global styled components and import them to a range of components that require an identical SVG style
  • We can flip the above point, where each component could define unique styling to an SVG, on a per-component basis. The SVG itself will not be importing any unneeded styles or attaching any unnecessary stylesheets along the way
  • We have the Styled Component ecosystem at hand, such as access to props and theming capabilities, where we can easily modify a catalogue of SVGs based on the current theme of the app

Being able to theme SVGs is perhaps the best use case of combining Styled Components and SVGs. Gone are the days where we have to switch images or other assets to maintain a theme of a website — it is now trivially easily to do just this, with one SVG and access to its properties.

Note: You cannot access SVG properties if you embed them within an <img /> tag. Although this is commonly done in less enlightened projects, I would strongly advise to use the SVGR approach.

Exploring Stroke Animation with Theme Toggle SVG

A great example of what we have been discussing is a theme toggle solution I coded for an upcoming application, where SVGs are combined with CSS animation to achieve the following effect:

Theme toggling with an SVG for upcoming JKRB website

The toggle animation here is purely CSS based (no Javascript), relying on a few CSS properties to manipulate the stroke of the SVG. We’ll cover how to achieve this next. The JKRB Investments logo is also an imported SVG, also totally theme-able. This SVG styling is seamlessly integrated with the Styled Components of the app. When we click the toggle, the theme state change triggers a re-render of our SVGs, transitioning them to the updated theme styling.

But what styles are we actually manipulating here? Let’s overview the main properties at a high level before breaking down the details:

  • The fill property of the JKRB logo determines its colour. The fill value itself is determined by the current theme; in this case, either light or dark:
// wrapping SVGs in theme-able componentsimport styled from 'styled-components';
import theme from 'styled-theming';
export const backgroundColor = theme('mode', {
light: '#fafafa',
dark: '#0e0f11'
});
const LogoWrapper = styled.div`
svg {
fill: ${backgroundColor};
}
`;

Note: If you are interested in theming your React apps, take a look at my insight that introduces the concept, and walks through exactly how to use Styled Components in conjunction with Styled Theming:

  • The stroke property of the theme toggle determines its colour, the value of which is also derived from the current theme
  • The stroke-dasharray and stroke-dashoffset properties achieve the stroke animation, as well as stroke-width allowing us to change the thickness of the stroke
  • @keyframe animation is used to transition the stroke, as well as the opacity of the sun shine

So we have combined SVGs, CSS styling, keyframe animations and a theme-able Styled Components setup to achieve this effect.

Perhaps the most interesting part is the toggle animation itself. This is achieved is by breaking down the SVG into multiple paths, and controlling their stroke animation individually. Let’s look at this in more detail next.

Setting up SVG Stroke Animations

Note: The stroke of an SVG is most likely a path XML tag in the SVG file itself. In the following explanations we will be using the stroke terminology, but keep in mind the stroke itself is represented as tags in the SVG.

The stroke of an SVG can be controlled via CSS in various ways. We mentioned two key properties above, stroke-dasharray and stroke-dashoffset (Links to Mozilla web docs).

These are the only two properties needed to create a stroke animation. Let’s examine how they work.

The stroke-dasharray property

stroke-dasharray determines the length of dashes of a stroke. It turns a solid line into a dashed one. The larger the number, the larger the spaces between those dashes. Imagine if we had a canvas of 1000px by 1000px. We draw a line across that canvas. We then assign the stroke-dasharray a value of 1000:

.line {
stroke-dasharray: 1000;
}

What happens here is that the space between the dashes will be the entire length of the stroke itself. The stroke will look like a solid line still, as the spacing will not start until the end of the stroke.

Wouldn’t it be handy if we could now offset this dash by 100%, so we start with a totally invisible stroke, then animate it in as through the line is being drawn? Perhaps it would — and this brings us onto the next property, stroke-dashoffset.

The stroke-dashoffset property

stroke-dashoffset determines how far the dash rendering will start from its origin — the origin being the start of the stroke. In other words, it defines an offset on the rendering of the associated dash array.

Let’s take the above dash array, where each dash is the size of the stroke itself. Let’s give the dash offset a value of 1000 too.

.line {
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
}

Now the stroke will be invisible — Since the spacing between dashes is the entire length of the stroke, and the visible dash has also been offset by that entire stroke length.

By relying on this mechanism and animating these properties, either with @keyframes or transition, we can animate strokes in and animate strokes out. Consider adding this animation to the mix:

// animating the dash offset to "draw" the stroke@keyframes draw {
from {
stroke-dashoffset: 1000;
}
to {
stroke-dashoffset: 0;
}
}

Attaching this animation to our line class would move the dash offset back to its origin, showing the stroke in its entirety. Simply attach this animation to our line to see the effects:

// animating our line to be "drawn" in.line {
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation-name: draw;
animation-duration: 1s;
animation-fill-mode: forwards;

}

Note: For complex CSS like this, it is more readable and manageable to use each animation- property, rather than the unified animation property that combines an animation configuration into one value.

The animation-fill-mode property set to forwards ensures that the SVG will maintain the animation end state upon finishing, instead of jumping back to the pre-animated state where the stroke was not showing at all.

It is just as simple to reverse this effect — starting with a fully drawn stroke, and transitioning it out. You could either define another animation with the stroke-dashoffset transitioning to 0 to 1000, or reverse the draw animation using the animation-direction property:

// un-drawing a line.line {
stroke-dasharray: 1000;
stroke-dashoffset: 0;
animation-name: draw;
animation-duration: 1s;
animation-fill-mode: forwards;
animation-direction: reverse;
}

Notice now that the default stroke-dashoffset is now 0 to ensure the entire dash is showing in its default state.

Integrating styled components and theming

This concept could now be taken even further in the context of Styled Components. Let’s say that we want to animate-in a moon when we switch to dark mode, and animate-out the moon when we switch to a light mode.

We firstly import the SVG as a React component using Webpack’s built-in SVGR support:

import { ReactComponent as MoonSVG } from './moon.svg';

Now we can define the styling for MoonSVG, which is trivially simple to do with Styled Components — we just need to set the animation-direction property of the SVG, that is based on the current theme of your app:

// animate strokes based on theme// fetching some theme context
const theme = useTheme();
const StyledMoon = styled.div`
@keyframes draw {
from {
stroke-dashoffset: 1000;
}
to {
stroke-dashoffset: 0;
}
}
path {
stroke-dasharray: 1000;
stroke-dashoffset: ${theme.mode === 'dark' ? 1000 : 0 };
animation-name: draw;
animation-duration: 1s;
animation-fill-mode: forwards;
animation-direction: ${theme.mode === 'dark' ? `normal` : `reverse`};
}

`;

Notice that the stroke-dashoffset is also determined based on our current theme. Not only this, we have wrapped the properties in a path block. StyledMoon is not the SVG itself, but the element that wraps our SVG. Therefore we are saying that any SVG element within StyledMoon will inherit these properties.

Note: You could implement a class or id attribute instead of path to limit the styling to a particular segment of an SVG, in the case StyledMoon is wrapping multiple SVGs.

From here we can render the SVG and our styled component together to see the animation take effect:

...const MyComponent = () => {   return(
<StyledMoon>
<MoonSVG />
</StyledMoon>
);
}

Animating Multiple Paths

The theme toggle showcased earlier is actually a combination of 4 paths; one static path and three animated paths. By giving each path its own class or id, we can refer to each path individually to apply unique styling and animation behaviour to each.

Note: You could also export each path as a separate SVG and wrap them all with a Styled Component, as we discussed above.

With only a basic stroke colour applied, this is what all 4 paths combined resemble:

The Theme Toggle component in its entirety

Can you determine which parts of this SVG need to be animated, and therefore separated into different paths?

  • The sun shine surrounding the sun / moon fade in and out via the opacity property, and do not actually have stroke animation applied to them
  • The left side of the sun / moon is the only static path that does not animate
  • The right side of the sun is a separate path, that animates out when switching to dark mode
  • The moon curve is a separate path, that animates in when switching to dark mode

To sum up, here is the SVG broken down into its various segments:

The SVG segments of the theme toggle

In Summary

This article has discussed the means of importing SVGs into a React project, and how to manipulate them via CSS and Styled Components.

We have also delved into animation techniques, with stroke animation in-particular, which I find to be quite an under-used technique on the web today.

I have also published an article dedicated to explaining how stroke animations work that coincides with another use case here:

I hope these insights have given you encouragement to think about where these method could be incorporated into your projects. These UX enhancements will ultimately make for a better user experience, and strengthen your brand in the process.

Ross Bulat

Written by

Programmer and Author. Director @ JKRBInvestments.com. Creator of ChineseGrammarReview.app for iOS.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade