There is a moment in most React Native projects where a designer hands over an icon set as SVG files and someone on the team says “just export them as PNGs at 3x”. Do that and you have signed up for three files per icon, no runtime recolouring, and a fuzzy logo on the next device that ships with a new screen density. The alternative is react-native-svg, which has been the answer to this for about a decade and is still the single most installed library in the React Native ecosystem that most people never think about.
Versions here are pinned to react-native-svg 15.15.5. It is MIT, maintained by Software Mansion, sits at roughly 8,000 GitHub stars, and pulls about 5.7 million downloads a week. That number is worth staring at. If you use React Navigation, Reanimated charts, an icon library, or almost any UI kit, this is already in your lockfile.
What react-native-svg gives you
Two separate things, and mixing them up is the cause of most of the confusion:
- A set of React components that mirror the SVG spec.
Svg,Path,Circle,Rect,G,Defs,LinearGradientand the rest. You write SVG as JSX and it renders to real native drawing primitives on iOS, Android and web. - Runtime parsers for SVG markup.
SvgXmlfor a string you already have andSvgUrifor one you have to fetch, plusSvgCssandSvgCssUrifromreact-native-svg/csswhen the file contains a<style>element.
There is a third path that is not in the library at all, and it is the one you probably want for icons: compile time conversion with react-native-svg-transformer, so that import Logo from './logo.svg' just works. More on that below.
Installation
On Expo:
npx expo install react-native-svg
On bare React Native:
npm install react-native-svg
cd ios && pod install
That is genuinely it. No config plugin, no manifest edits, no keys. One compatibility line to know: from 15.13.0 onward the library requires React Native 0.78 or newer. If you are stuck below that, pin to 15.12.x rather than fighting the build.
Worth noting for planning purposes: 15.15.5 shipped in May 2026 and the repo has been active since, with commits as recent as this month. A library this stable going a few months between releases is not a red flag, it is what “finished” looks like for something that tracks a frozen spec.
Drawing something
import Svg, {Circle, Path} from 'react-native-svg';
export function Smiley({size = 100}) {
return (
<Svg width={size} height={size} viewBox="0 0 100 100">
<Circle cx="50" cy="50" r="45" fill="#ffd93d" />
<Circle cx="35" cy="40" r="5" fill="#0d1b2a" />
<Circle cx="65" cy="40" r="5" fill="#0d1b2a" />
<Path
d="M 30 62 A 22 22 0 0 0 70 62"
stroke="#0d1b2a"
strokeWidth="6"
strokeLinecap="round"
fill="none"
/>
</Svg>
);
}
The important prop in there is viewBox. It defines the internal coordinate system, which means every number inside the SVG is independent of the rendered size. Set viewBox once and you can render the same component at 24 points in a tab bar and 240 in a hero without touching anything else. Omit it and your paths are locked to pixel coordinates, which is the usual reason an icon “looks fine in Figma and wrong in the app”.
Props are camelCase, and colours inherit
SVG attributes become camelCase props: stroke-width is strokeWidth, stop-color is stopColor, clip-path is clipPath. Common props across every element include fill (default #000), stroke (default none), strokeWidth, strokeLinecap, strokeLinejoin, strokeDasharray, strokeDashoffset, fillRule, and transform props x, y, rotation, scale, origin.
Children inherit paint from the Svg element, and there is a small feature hiding in that: set color on the root and use fill="currentColor" or stroke="currentColor" on children. That single mechanism is how you build a themable icon set without editing a single path.
<Svg viewBox="0 0 24 24" color={theme.icon}>
<Path d="..." fill="currentColor" />
</Svg>
The setup you actually want for icons
Hand-converting path data into JSX is fine for one shape and miserable for forty. Use react-native-svg-transformer (1.5.3) so Metro converts .svg files at build time, with cached transforms.
Expo, in metro.config.js:
const {getDefaultConfig} = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer.babelTransformerPath = require.resolve(
'react-native-svg-transformer/expo',
);
config.resolver.assetExts = config.resolver.assetExts.filter(
ext => ext !== 'svg',
);
config.resolver.sourceExts = [...config.resolver.sourceExts, 'svg'];
module.exports = config;
Bare React Native 0.72 or newer:
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const defaultConfig = getDefaultConfig(__dirname);
const {assetExts, sourceExts} = defaultConfig.resolver;
module.exports = mergeConfig(defaultConfig, {
transformer: {
babelTransformerPath: require.resolve(
'react-native-svg-transformer/react-native',
),
},
resolver: {
assetExts: assetExts.filter(ext => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg'],
},
});
Note the two different entry points. Getting those crossed over is a common cause of “it works locally and breaks in EAS Build”. A React Native project that uses Expo modules but not the Expo CLI wants the /expo entry.
Then:
import Logo from './assets/logo.svg';
<Logo width={120} height={40} fill={theme.brand} />
TypeScript will complain about the import until you declare the module, so add a declarations.d.ts:
declare module '*.svg' {
import React from 'react';
import {SvgProps} from 'react-native-svg';
const content: React.FC<SvgProps>;
export default content;
}
And restart Metro with --reset-cache the first time. The transformer caches aggressively, which is the point, and also the reason your first attempt appears to do nothing.
When to use SvgXml and SvgUri instead
The compile time route needs the file at build time. When the SVG arrives at runtime, use the parsers:
import {SvgXml, SvgUri} from 'react-native-svg';
<SvgXml xml={svgStringFromApi} width="100%" height={200} />
<SvgUri uri="https://example.com/chart.svg" width={200} height={200} />
Two things the docs are honest about and people miss. First, SvgUri and SvgCssUri log errors to the console and otherwise ignore them, so a failed fetch renders nothing with no callback. If that matters, fetch the file yourself and hand the string to SvgXml so you own the error and loading states. Second, if the markup contains a <style> block, the plain parsers will not apply it. Import SvgCss or SvgCssUri from react-native-svg/css instead. That subpath import is the entire fix for “my SVG renders but everything is black”.
Runtime parsing also costs real work on every mount. For an icon set, compile time wins every time.
Animating SVG
There is no animation system inside react-native-svg. You bring Reanimated and animate props.
import Animated, {useAnimatedProps, useSharedValue, withTiming} from 'react-native-reanimated';
import Svg, {Circle} from 'react-native-svg';
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
export function Ring({progress}) {
const CIRCUMFERENCE = 2 * Math.PI * 45;
const animatedProps = useAnimatedProps(() => ({
strokeDashoffset: CIRCUMFERENCE * (1 - progress.value),
}));
return (
<Svg width={120} height={120} viewBox="0 0 100 100">
<AnimatedCircle
cx="50" cy="50" r="45"
stroke="#61dafb" strokeWidth="8" fill="none"
strokeDasharray={CIRCUMFERENCE}
animatedProps={animatedProps}
/>
</Svg>
);
}
The strokeDasharray plus strokeDashoffset pair is the trick behind every progress ring, every drawn-line reveal and most loading spinners you have admired. Set the dash length to the full path length, then animate the offset from that length down to zero.
Two cautions. Animating d on a Path is expensive and often visibly janky, so animate transforms, offsets and opacity instead where you can. And if you are animating dozens of SVG elements at 60fps or doing per-frame path generation, you have left this library’s comfort zone. That is what React Native Skia is for. Skia draws to a canvas with a shader pipeline; react-native-svg builds a native view tree. Different tools, different ceilings.
Performance notes
- Every element is a native view. A 600 node illustration is 600 views. Simplify paths in your design tool before you ship them, or rasterise genuinely complex artwork.
- Do not rebuild the component on every render. Wrap icon components in
React.memo, especially in list rows. - Watch imported icon sets. Exports from design tools routinely carry clip paths, empty groups and metadata that do nothing. Run them through SVGO first.
- Prefer compile time conversion. Parsing markup at runtime is measurable work on a cold list render.
- Keep
ForeignObjectfor special cases. It lets you put React Native views inside an SVG, which is genuinely useful for text and images under a mask, but it is the heaviest thing in the library.
Useful elements people forget exist
Mask plus a gradient fill is how you fade an image or a chart out at its edge without exporting a faded asset. ClipPath gives you any non rectangular crop. Pattern tiles a shape across a fill, which is how you get a hatched region without an image. Marker puts arrowheads or vertex shapes on a path. TextPath runs text along a curve. Use and Symbol let you define once and instance many times, which keeps a repeated shape from bloating your tree.
These are all spec features rather than library inventions, which is the quiet advantage of react-native-svg: anything you learn transfers to the web, and anything you already know from the web transfers here. If you work across both, the differences between React and React Native are smaller in this corner than almost anywhere else.
Debugging
Nothing renders. Check for a viewBox, check the Svg has width and height, and check your paths are not outside the viewBox. The library will happily draw nothing.
Everything is black. Fills are defined in a <style> block the plain parser ignores. Use SvgCss.
The icon will not take a colour. The source has hardcoded fill attributes on its paths. Either strip them in SVGO or configure SVGR through the transformer to replace them with currentColor.
Import of a .svg file fails after adding the transformer. Restart Metro with --reset-cache, and check you used the entry point that matches your project type.
FAQ
Is react-native-svg still maintained? Yes. Software Mansion maintains it, the repo is active, and 15.15.5 is the current release. The gap between releases reflects a stable library tracking a stable spec.
Do I need it if I only use an icon library? You already have it. Most icon packages list it as a peer dependency.
Can I use it on the New Architecture? Yes. Fabric support has been in since 13.x and the current release requires React Native 0.78 or newer, which is New Architecture territory anyway.
SVG or Skia? SVG for icons, illustrations, simple charts and anything that maps to declarative shapes. Skia for canvas work, shaders, per frame drawing and heavy animation.
Does it work on web? Yes, through react-native-web, and it renders to real DOM SVG there.