SVG in React Native: react-native-svg explained

React Native has no svg element. How to draw with react-native-svg, import .svg files with react-native-svg-transformer, and show SVG from a string or a URL.

Published

React Native cannot draw SVG by itself. There is no <svg> element, because there is no browser, and SVG is not among the formats the built-in Image component lists. The standard answer is the react-native-svg library, which provides the SVG elements as components with capital letters: <Svg>, <Circle>, <Path>. This page follows the documentation for react-native-svg 15. Its examples were checked against that documentation and not run on a device.

import Svg, { Circle, Rect } from 'react-native-svg';

export default function Badge() {
  return (
    <Svg width="100" height="100" viewBox="0 0 100 100">
      <Circle cx="50" cy="50" r="45" stroke="blue" strokeWidth="2.5" fill="green" />
      <Rect x="15" y="15" width="70" height="70" stroke="red" strokeWidth="2" fill="yellow" />
    </Svg>
  );
}

Install

With Expo:

npx expo install react-native-svg

Without Expo:

npm install react-native-svg
cd ios && pod install

The second line links the native code on iOS. The library runs on iOS, Android, macOS and Windows, and on the web through a compatibility layer.

Versions matter, because the library contains native code. Its README lists which releases need which React Native:

react-native-svgNeeds React Native
15.13 and later0.78 or later
15.8 and later0.73 or later
15.0 and later0.70 or later
12.3 and later0.64 or later

npx expo install chooses a version that works with the React Native in the project.

Four ways to show an SVG

You haveUseNeeds
Shapes to write by hand, or data to drawThe components: <Svg>, <Path> and the restreact-native-svg
.svg files in the projectImport each file as a componentreact-native-svg-transformer
SVG markup in a string<SvgXml xml={text} />react-native-svg
An SVG at a web address<SvgUri uri="https://..." />react-native-svg

1. The components

Each SVG element has a component of the same name with a capital first letter. The props are the SVG attributes in camelCase, as in React on the web.

SVGreact-native-svg
<svg><Svg>
<g><G>
<path>, <rect>, <circle>, <ellipse>, <line>, <polygon>, <polyline><Path>, <Rect>, <Circle>, <Ellipse>, <Line>, <Polygon>, <Polyline>
<text>, <tspan>, <textPath><Text>, <TSpan>, <TextPath>
<defs>, <use>, <symbol><Defs>, <Use>, <Symbol>
<linearGradient>, <radialGradient>, <stop><LinearGradient>, <RadialGradient>, <Stop>
<clipPath>, <mask>, <pattern>, <marker><ClipPath>, <Mask>, <Pattern>, <Marker>
<image><Image>
stroke-width="2"strokeWidth="2"
class and a <style> elementRead only by SvgCss, for markup held in a string. In component code, style with props

Two names clash with React Native’s own. Text and Image from react-native-svg are the SVG elements, not the ordinary components, so rename one of them when a file needs both:

import { Text } from 'react-native';
import Svg, { Text as SvgText } from 'react-native-svg';

Colour. fill and stroke work as in SVG. For an icon that takes its colour from outside, set the color prop on <Svg> and use currentColor inside:

import Svg, { Path } from 'react-native-svg';

export function CheckIcon({ size = 24, color = 'black' }) {
  return (
    <Svg width={size} height={size} viewBox="0 0 24 24" color={color}>
      <Path d="M5 13l4 4L19 7" fill="none" stroke="currentColor" strokeWidth="2" />
    </Svg>
  );
}

The color prop paints nothing itself. It sets the value that currentColor stands for in the shapes below.

Size. Give <Svg> a width and height, as numbers or as percentages, and a viewBox so the drawing scales to fit. SVG viewBox explained covers the four numbers, which mean the same here.

Touch. Shapes take onPress, onPressIn, onPressOut and onLongPress:

<Circle cx="50" cy="50" r="40" fill="red" onPress={() => alert('Pressed')} />

2. Import .svg files as components

Writing components by hand suits a few icons. For a folder of .svg files, react-native-svg-transformer turns each import into a component at build time:

import Logo from './logo.svg';

export default function Header() {
  return <Logo width={120} height={40} />;
}

Install it beside react-native-svg:

npm install --save-dev react-native-svg-transformer

Then tell Metro, React Native’s bundler, to treat .svg as source code and not as an asset. Merge this into metro.config.js, creating the file if there is none.

For Expo, SDK 41 or newer:

const { getDefaultConfig } = require("expo/metro-config");

module.exports = (() => {
  const config = getDefaultConfig(__dirname);

  const { transformer, resolver } = config;

  config.transformer = {
    ...transformer,
    babelTransformerPath: require.resolve("react-native-svg-transformer/expo")
  };
  config.resolver = {
    ...resolver,
    assetExts: resolver.assetExts.filter((ext) => ext !== "svg"),
    sourceExts: [...resolver.sourceExts, "svg"]
  };

  return config;
})();

For React Native 0.72.1 or newer, without Expo:

const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");

const defaultConfig = getDefaultConfig(__dirname);
const { assetExts, sourceExts } = defaultConfig.resolver;

const config = {
  transformer: {
    babelTransformerPath:
      require.resolve("react-native-svg-transformer/react-native")
  },
  resolver: {
    assetExts: assetExts.filter((ext) => ext !== "svg"),
    sourceExts: [...sourceExts, "svg"]
  }
};

module.exports = mergeConfig(defaultConfig, config);

Restart Metro after changing its configuration.

TypeScript. Add a declaration so that .svg imports are typed as components. In declarations.d.ts:

declare module "*.svg" {
  import React from "react";
  import { SvgProps } from "react-native-svg";
  const content: React.FC<SvgProps>;
  export default content;
}

Colour from a prop. The transformer uses SVGR, the same converter that web build tools use, and reads its configuration file. This .svgrrc makes every black fill take the component’s fill prop:

{
  "replaceAttrValues": {
    "#000": "{props.fill}"
  }
}

With that, <Logo fill="tomato" /> recolours any shape whose fill in the file is exactly #000.

Jest. Tests do not go through Metro, so .svg imports need a mock. The transformer’s README gives one: a file __mocks__/svgMock.js containing module.exports = "SvgMock";, mapped in the Jest configuration with "moduleNameMapper": { "\\.svg": "<rootDir>/__mocks__/svgMock.js" }.

3. SVG from a string

When the markup arrives as text, from an API or a constant, SvgXml parses and draws it:

import { SvgXml } from 'react-native-svg';

const xml = `
  <svg width="32" height="32" viewBox="0 0 32 32">
    <rect width="32" height="32" rx="6" fill="teal" />
  </svg>
`;

export default function Tile() {
  return <SvgXml xml={xml} width="100%" height="100%" />;
}

SvgXml does not read CSS. If the markup has a <style> element with class rules, as exports from design programs often do, use SvgCss from the react-native-svg/css entry point, which takes the same props.

The string is parsed while the app runs. For files known at build time, the transformer in section 2 does that work during the build.

4. SVG from a URL

import { SvgUri } from 'react-native-svg';

export default function RemoteLogo() {
  return (
    <SvgUri
      width="100%"
      height="100%"
      uri="https://example.com/logo.svg"
      onError={(error) => console.log(error.message)}
    />
  );
}

SvgUri fetches the file and draws it. If the file cannot be loaded it logs the error and otherwise carries on, drawing nothing, unless you handle it: onError reports the failure and a fallback prop takes a component to show in its place. For remote files that use a <style> element, the CSS version is SvgCssUri from react-native-svg/css.

Converting an existing SVG

To turn SVG markup into component code once, SVGR has a React Native mode. On the command line it is the --native option, and the SVGR playground has the same switch. It capitalises the element names, adds the react-native-svg import, and removes elements the library does not support.

Clean the file first. The SVG optimizer strips editor data and comments and shortens the numbers, which makes the result far easier to read. Keep the viewBox: without it the drawing will not scale to the size you give the component.

As a plain image

If the SVG only needs showing, with nothing inside it changed, an image component that understands SVG is less work. In Expo, the Image component from expo-image lists SVG among its supported formats on Android, iOS and the web. The Expo documentation notes one limit on iOS: the system decoder can misdraw some valid arc commands in path data, and it points to react-native-svg for fuller coverage.

Otherwise, export the artwork as PNG at two and three times its size and use the ordinary Image. SVG to PNG makes the files.

Limits to know

  • CSS is limited. A <style> element is read only by SvgCss and SvgCssUri. Everything else is styled with props.
  • Filters are partial. The documentation says that not all filters are implemented on the native platforms yet, and some show a warning instead of an effect. It lists blur, drop shadow, offset, flood, blend, composite, colour matrix and merge as implemented, and the lighting, turbulence, morphology, displacement and convolve filters among those not yet supported.
  • Fonts in <Text>. The transformer’s README says custom font families are not supported on iOS out of the box, and suggests converting text to outlines in the drawing program.
  • Radial gradients. The README lists one known issue: the focus point of a RadialGradient cannot be applied on Android.

Common problems

What you seeCauseFix
An imported .svg does not render as a componentMetro treats .svg as an assetAdd the transformer to metro.config.js and restart Metro
TypeScript: cannot find module ./logo.svgNo declaration for .svgAdd the declare module "*.svg" block
Colours are missing from markup shown with SvgXml or SvgUriThe markup styles its shapes with a <style> elementSvgCss or SvgCssUri, or export with attributes instead of CSS
A remote SVG shows nothingSvgUri ignores load errors by defaultAdd onError and fallback
Two components called Text, or two called Image, in one fileBoth React Native and the library export those namesImport the SVG one under another name, such as SvgText
The drawing is cropped or will not scaleNo viewBox on <Svg>Add viewBox="0 0 W H" with the original size
Jest fails on an .svg importTests do not use the Metro transformerMock .svg files with moduleNameMapper

Questions

Is react-native-svg included in Expo? The Expo Go app ships with its native code. Add the JavaScript package to the project with npx expo install react-native-svg.

Can I use the same SVG code on the web and in the app? The components run on the web through the library’s compatibility layer, and react-native-svg-transformer exists so that import Logo from './logo.svg' behaves the same in a web build that uses SVGR. Plain <svg> JSX from a web project has to be converted. SVG in React covers the web side.

Why does my SVG look different from the browser? The library draws SVG with its own code on each platform, not with a browser engine, and it supports most of SVG, not all of it. Simplify the file: plain paths, attribute styling, no filters.