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-svg | Needs React Native |
|---|---|
| 15.13 and later | 0.78 or later |
| 15.8 and later | 0.73 or later |
| 15.0 and later | 0.70 or later |
| 12.3 and later | 0.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 have | Use | Needs |
|---|---|---|
| Shapes to write by hand, or data to draw | The components: <Svg>, <Path> and the rest | react-native-svg |
| .svg files in the project | Import each file as a component | react-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.
| SVG | react-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> element | Read 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 bySvgCssandSvgCssUri. 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
RadialGradientcannot be applied on Android.
Common problems
| What you see | Cause | Fix |
|---|---|---|
| An imported .svg does not render as a component | Metro treats .svg as an asset | Add the transformer to metro.config.js and restart Metro |
TypeScript: cannot find module ./logo.svg | No declaration for .svg | Add the declare module "*.svg" block |
Colours are missing from markup shown with SvgXml or SvgUri | The markup styles its shapes with a <style> element | SvgCss or SvgCssUri, or export with attributes instead of CSS |
| A remote SVG shows nothing | SvgUri ignores load errors by default | Add onError and fallback |
Two components called Text, or two called Image, in one file | Both React Native and the library export those names | Import the SVG one under another name, such as SvgText |
| The drawing is cropped or will not scale | No viewBox on <Svg> | Add viewBox="0 0 W H" with the original size |
| Jest fails on an .svg import | Tests do not use the Metro transformer | Mock .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.