React SVGs
What follows is a quick rundown of how we’re currently handling SVGs at ShakaCode.
Our philosophy is to make a component for each SVG and (to the extent possible) leave styling up to CSS. In the interest of simplicity, we’ve decided not to care about using sprites or refs to improve performance, as doing so would greatly increase complexity without guarantee of any noticeable gain. For more info on this and SVGs in React generally, see this awesome article by Sarah Drasner over at CSS-tricks that served as the basis for our architecture.
Setup: Make an SvgTemplate Component
To aid in creating our SVG components, we first created a generic SVG utility component that serves as a sort of wrapper for each of our SVGs’ path
code. It looks like this:
// @flow
import React from 'react';
type Props = {
children?: React.Element<*>,
title: string,
viewBoxMinWidth?: number,
viewBoxMinHeight?: number,
viewBoxWidth: number,
viewBoxHeight: number,
};
const SvgTemplate = (
{
children,
title,
viewBoxMinWidth = 0,
viewBoxMinHeight = 0,
viewBoxWidth,
viewBoxHeight,
...otherProps
}: Props,
) => (
<svg
viewBox={`${viewBoxMinWidth} ${viewBoxMinHeight} ${viewBoxWidth} ${viewBoxHeight}`}
xmlns="http://www.w3.org/2000/svg"
aria-labelledby={`${title}-title`}
{...otherProps}
>
<title id={`${title}-title`}>{title}</title>
{children}
</svg>
);
export default SvgTemplate;
Optimizing the SVG
We always make sure to run our SVGs through the web interface for SVGOMG. It’s great for stripping unnecessary metadata and other cruft left behind by tools like Illustrator or Sketch, and can also do some optimizations to make the SVG code more efficient (that is, effect the same outcome with fewer characters). We run it with pretty much every optimization enabled, except we leave the viewBoxHeight
and viewBoxWidth
intact.
Creating the SVG Component
With the optimized code in-hand, we then create our component using the SvgTemplate
component shown above to keep the code DRY. Here is an example IconCaret
component where we provide some overridable defaults:
// @flow
import React from 'react';
import SvgIconTemplate from 'libs/templates/SvgIconTemplate';
type Props = {
title?: string,
viewBoxWidth?: number,
viewBoxHeight?: number,
};
const IconCaret = (
{
title = 'Caret',
viewBoxWidth = 12,
viewBoxHeight = 7,
...otherProps
}: Props,
) => (
<SvgIconTemplate {...{ title, viewBoxWidth, viewBoxHeight, ...otherProps }}>
<polygon stroke="none" fillRule="evenodd" points="1 0 6 0 11 0 12 1 6 7 0 1" />
</SvgIconTemplate>
);
export default IconCaret;
Styling the SVG Component
Note that we made sure not to include a fill
attribute in the above component; doing so is essentially an inline style, which takes precedence over any styles set in an external stylesheet. Also note that we do not define a height
or width
as, again, we want to be able to use CSS to configure this (using CSS to style SVGs helps to keep the content code separate from the style code). If you assign a className of .IconCaret
to the SVG component, you would be able to style it as follows:
.IconCaret {
width: 20px;
height: 20px;
fill: #000;
}
Sometimes you may need to apply styles directly to SVG element tags such as rect
or path
, it just depends on the SVG:
.IconCaret path {
// ...
}
Gotcha: Namespace Collisions when Optimizing
We started noticing that on pages where we rendered many SVG components, some of the icons would start to look very strange—like they were warped or had paths missing.
Eventually, we realized the issue was due to namespace collisions with different components’ g
tags’ ids. When SVG code through an optimizer like SVGOMG, it will minimize the id names to a
, b
, etc. Since it does this each time, multiple components had ids like a
or b
. Our workaround was to manually choose unique ids for each SVG component type.