In the world of modern web and mobile application development, SVG (Scalable Vector Graphics) has become an essential asset format. SVGs provide crisp, resolution-independent graphics that look great on any device. However, working with SVGs in React and React Native applications can be challenging without the right tools.
Enter SVGR - a powerful utility that transforms SVG files into ready-to-use React components. SVGR simplifies the process of integrating vector graphics into your React applications by converting static SVG files into dynamic, customizable components.
This article will guide you through setting up and using SVGR in your React Native projects, exploring both basic configurations and advanced customization techniques. We'll cover different approaches to transform your SVGs, manage color schemes, handle size responsiveness, and implement custom templates for greater control.
Whether you're working with icons, illustrations, or complex graphics, SVGR provides a seamless workflow that integrates with your development process. By the end of this article, you'll have a solid understanding of how to leverage SVGR to enhance your application's visual elements while maintaining performance and flexibility.
Let's dive into the details of implementing SVGR in your React Native project, starting with the basic setup and moving toward more advanced customization options.
This guide walks through the process of setting up SVGR in an Expo project to easily transform SVGs into React Native components.
# Install the Expo CLI if you don't have it already
npm install -g expo-cli
# Create a new Expo project
expo init my-svg-app
cd my-svg-app
npm install --save-dev react-native-svg @svgr/cli @svgr/babel-plugin-replace-jsx-attribute-value
npm install react-native-svg
Create a svgr
directory and a .svgrrc.js
file inside it:
mkdir -p svgr/helpers
touch svgr/.svgrrc.js
On start let's use simple configuration:
module.exports = {
outDir: './components/icons',
native: true,
typescript: true,
exportType: 'default',
expandProps: 'end',
};
The SVGR configuration provides several options to control how SVG files are transformed into React components:
outDir
: Specifies the output directory for generated componentsnative
: Generates React Native compatible components using react-native-svg
typescript
: Generates TypeScript filesexportType
: Sets the export type ('default' for default exports)expandProps
: Controls where additional props are spread ('end' places them at the end of props)You can find all configuration options in the SVGR documentation.
For more advanced transformations, you can use SVGR's Babel plugins. Here's an example of using these pluginsfor color manipulation and attribute cleanup:
module.exports = {
// ... other options from above
jsx: {
babelConfig: {
plugins: [
[
"@svgr/babel-plugin-replace-jsx-attribute-value",
{
values: [
{ value: "#000", newValue: "#fff" },
{ value: "#6c63ff", newValue: "props.primaryColor || \"#ff6363\"", literal: true },
{ value: "#ff6363", newValue: "props.secondaryColor || \"#6c63ff\"", literal: true }
]
}
],
[
"@svgr/babel-plugin-remove-jsx-attribute",
{
elements: ["Svg"],
attributes: ["xmlns", "className"]
}
]
]
}
}
};
1. Color Replacement:
The babel-plugin-replace-jsx-attribute-value
plugin is used to dynamically replace color values:
#000
is replaced with #fff
#6c63ff
is replaced with props.primaryColor
(with a fallback to
#ff6363
)#ff6363
is replaced with props.secondaryColor
(with a fallback to #6c63ff
)literal: true
option means the new value will be treated as a JavaScript expression rather than a string
2. Attribute Removal:
The babel-plugin-remove-jsx-attribute
plugin removes unnecessary attributes in this case it removes xmlns
and className
attributes from Svg
elements.
The final result will look like this. It’s not perfect as we are missing type for our props.
import * as React from "react";
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgApiInterfaceIcon = (props: SvgProps) => (
<Svg width={24} height={24} viewBox="0 0 1024 1024" {...props}>
<Path fill="#FFF" d="m432.4 453.5-17 46.7h34.4z" />
<Path
fill="#FFF"
d="M725.3 259.7H312.2c-16.5 0-30 13.5-30 30v413.1c0 16.5 13.5 30 30 30h413.1c16.5 0 30-13.5 30-30V289
/>
<Path
fill="#FFF"
d="M569.4 479.2c3.4-1.3 6-3.4 7.9-6.2s2.9-6.1 2.9-9.8c0-4.6-1.3-8.4-4-11.3-2.7-3-6.1-4.8-10.2-5.6q-4.5-.9 />
<Path
fill={props.secondaryColor || "#6c63ff"}
d="M648.4 677.5H352.6c-8.3 0-15.1-6.8-15.1-15.1v-295c0-5.5-4.5-10-10-10s-10 4.5-10 10v295c0 19.4 15.7 35
/>
<Path
fill={props.primaryColor || "#ff6363"}
d="M865 386.5c11 0 20-9 20-20s-9-20-20-20h-69.7v-56.8c0-38.6-31.4-70-70-70h-27.8v-67.3c0-11-9-20-2
/>
<Path
fill={props.primaryColor || "#ff6363"}
d="M407.6 521.4h50.3l11 28.6h27.6l-50.4-125.8h-26.9l-49 125.8h27zm24.8-67.9 17.3 46.7h-34.3zm103 49.1H
/>
</Svg>
);
export default SvgApiInterfaceIcon;
While SVGR's configuration options and Babel plugins provide significant customization, custom templates offerthe ultimate flexibility for tailoring your SVG components to specific project requirements. Custom templates allowyou to control exactly how the generated React components are structured, what props they accept, and howthey behave.
Creating Custom Templates
First, create these files in your svgr
directory:
touch svgr/svgrTemplate.js
touch svgr/svgrIndexTemplate.js
Update your SVGR configuration to use these custom templates by adding these lines (and removing the previous plugin configuration):
module.exports = {
// ...previous configuration
template: require('./svgrTemplate'),
indexTemplate: require('./svgrIndexTemplate'),
};
The index template controls how components are exported. This custom template ensures consistent naming conventions by:
const path = require('path');
function defaultIndexTemplate(filePaths) {
const exportEntries = filePaths.map((filePath) => {
const basename = path.basename(filePath.path, path.extname(filePath.path));
const exportName = /^\d/.test(basename) ? `Svg${basename}` : basename;
const finalName = exportName.includes('Icon') ? exportName : `${exportName}Icon`;
return `export { ${finalName} } from './${basename}'`;
});
return exportEntries.join('\n');
}
module.exports = defaultIndexTemplate;
The component template provides advanced control over how SVG components are generated:
const {
getAttributeValue,
removeProp,
mapCustomAttributesToProps,
replaceAttributeValue,
cleanup
} = require('./helpers');
const template = (
{ imports, componentName, jsx },
{ tpl: template },
) => {
// Extract the original width and height values from the SVG
const iconWidth = getAttributeValue(jsx, 'width')
const iconHeight = getAttributeValue(jsx, 'height')
// Calculate the original aspect ratio to maintain proportions when resizing
const ratio = Number(iconWidth) / Number(iconHeight)
// Standardize component naming - remove "Svg" prefix and ensure "Icon" suffix
componentName = componentName.replace('Svg', '')
componentName = componentName.includes('Icon') ? componentName : `${componentName}Icon`
// Remove unnecessary SVG attributes that aren't needed in React Native
removeProp(jsx)('xmlns') // XML namespace not needed in React
removeProp(jsx)('role') // Accessibility role handled differently in React Native
removeProp(jsx)('className') // CSS classes aren't used in React Native
// Replace static width/height with dynamic values from component props
replaceAttributeValue(jsx, 'width', {
type: 'JSXExpressionContainer', expression: {
type: 'Identifier',
name: 'width'
}
}, false);
replaceAttributeValue(jsx, 'height', {
type: 'JSXExpressionContainer', expression: {
type: 'Identifier',
name: 'height'
}
}, false);
// Make fill colors dynamic by using props.fill (supports indexed fills for multiple colors)
replaceAttributeValue(jsx, 'fill', {
type: 'JSXExpressionContainer', expression: {
type: 'MemberExpression',
object: { type: 'Identifier', name: 'props' },
property: { type: 'Identifier', name: 'fill' },
computed: false
}
}, true);
// Generate TypeScript interface for custom props
const customPropsString = mapCustomAttributesToProps();
// Store original dimensions as constants
const defaultWidthString = `const DEFAULT_WIDTH = ${iconWidth};`
const defaultHeightString = `const DEFAULT_HEIGHT = ${iconHeight};`
// Calculate dynamic size while preserving aspect ratio
const widthString = `const width = size;`
const heightString = `const height = size / ${ratio};`
// Assemble the complete component using the template literal
const result = template`
${imports}
${defaultWidthString}
${defaultHeightString}
${customPropsString}
export const ${componentName} = ({ size = DEFAULT_WIDTH, ...props }: SvgProps & Props) => {
${widthString};
${heightString};
return (
${jsx}
)};
`;
// Reset any state tracking for the next component
cleanup();
return result;
};
module.exports = template;
When you run SVGR with these custom templates, your SVG files will be transformed into React Nativecomponents with a consistent structure and intelligent defaults. Here's a complete example of what a generatedcomponent might look like:
import * as React from "react";
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const DEFAULT_WIDTH = 24;
const DEFAULT_HEIGHT = 24;
interface Props {
fill1?: string;
fill2?: string;
fill3?: string;
fill4?: string;
fill5?: string;
fill6?: string;
}
export const ApiInterfaceIcon = ({ size = DEFAULT_WIDTH, ...props }: SvgProps & Props) => {
const width = size;
const height = size / 1;
return (
<Svg width={width} height={height} viewBox="0 0 1024 1024" {...props}>
<Path fill={props.fill1 || "#FFF"} d="m432.4 453.5-17 46.7h34.4z" />
<Path
fill={props.fill2 || "#FFF"}
d="M725.3 259.7H312.2c-16.5 0-30 13.5-30 30v413.1c0 16.5 13.5 30 30 30h413.1c16.5 0 30-13.5 30-30V
289.7c0-16.6-13.5-30-30-30m-98.8 164.5h25.4V550h-25.4zm-116.5 0h40.8c15.5 0 25.5.6 30.2 1.9 7.2 1.9 13.
2 6 18.1 12.3s7.3 14.5 7.3 24.5q0 11.55-4.2 19.5c-4.2 7.95-6.4 9.4-10.7 12.4q-6.45 4.5-13.2 6c-6.1 1.2-14.8 1.8-2
6.4 1.8h-16.6V550H510zm-90.7 0h26.9L496.5 550h-27.6l-11-28.6h-50.3L397.2 550h-27zm229.1 273.3H352.6
c-19.4 0-35.1-15.7-35.1-35.1v-295c0-5.5 4.5-10 10-10s10 4.5 10 10v295c0 8.3 6.8 15.1 15.1 15.1h295.8c5.5 0 10 4.5 10 10s-4.4 10-10 10"
/>
<Path
fill={props.fill3 || "#FFF"}
d="M569.4 479.2c3.4-1.3 6-3.4 7.9-6.2s2.9-6.1 2.9-9.8c0-4.6-1.3-8.4-4-11.3-2.7-3-6.1-4.8-10.2-5.6q-4.
5-.9-18.3-.9h-12.3v35.7h13.9c10 .1 16.7-.6 20.1-1.9"
/>
<Path
fill={props.fill4 || "#6c63ff"}
d="M648.4 677.5H352.6c-8.3 0-15.1-6.8-15.1-15.1v-295c0-5.5-4.5-10-10-10s-10 4.5-10 10v295c0 19.4 1
5.7 35.1 35.1 35.1h295.8c5.5 0 10-4.5 10-10s-4.4 10-10 10"
/>
<Path
fill={props.fill5 || "#ff6363"}
d="M865 386.5c11 0 20-9 20-20s-9-20-20-20h-69.7v-56.8c0-38.6-31.4-70-70-70h-27.8v-67.3c0-11-920-20-20s-20 9-20 20v67.3H611v-67.3c0-11-9-20-20-20s-20 9-20 20v67.3h-46.5v-67.3c0-11-9-20-20-20s20 9-20 20v67.3H438v-67.3c0-11-9-20-20-20s-20 9-20 20v67.3h-85.8c-38.6 0-70 31.4-70 70v56.8h-69.7c11 0-20 9-20 20s9 20 20 20h69.7V433h-69.7c-11 0-20 9-20 20s9 20 20 20h69.7v46.5h-69.7c-11 0-20 9-20
20s9 20 20 20h69.7V606h-69.7c-11 0-20 9-20 20s9 20 20 20h69.7v56.8c0 38.6 31.4 70 70 70H343v72.5c0
11 9 20 20 20s20-9 20-20v-72.5h46.5v72.5c0 11 9 20 20 20s20-9 20-20v-72.5H516v72.5c0 11 9 20 20 20s2
0-9 20-20v-72.5h46.5v72.5c0 11 9 20 20 20s20-9 20-20v-72.5h82.8c38.6 0 70-31.4 70-70V646H865c11 0 2
0-9 20-20s-9-20-20-20h-69.7v-46.5H865c11 0 20-9 20-20s-9-20-20-20h-69.7V473H865c11 0 20-9 20-20s
-9-20-20-20h-69.7v-46.5zM755.3 702.7c0 16.5-13.5 30-30 30H312.2c-16.5 0-30-13.5-30-30v-413c0-16.5 1
3.5-30 30-30h413.1c16.5 0 30 13.5 30 30z"
/>
<Path
fill={props.fill6 || "#ff6363"}
d="M407.6 521.4h50.3l11 28.6h27.6l-50.4-125.8h-26.9l-49 125.8h27zm24.8-67.9 17.3 46.7h-34.3zm103 4
9.1H552q17.25 0 26.4-1.8 6.75-1.5 13.2-6c4.3-3 7.9-7.1 10.7-12.4s4.2-11.8 4.2-19.5c0-10-2.4-18.2-7.3-24.5s-1
0.9-10.4-18.1-12.3c-4.7-1.3-14.8-1.9-30.2-1.9H510V550h25.4zm0-57.1h12.3c9.2 0 15.2.3 18.3.9 4.1.7 7.5 2.6 10.
2 5.6s4 6.8 4 11.3c0 3.7-1 7-2.9 9.8s-4.6 4.9-7.9 6.2c-3.4 1.3-10.1 2-20.1 2h-13.9zm91.1-21.3h25.4V550h-25.4
z"
/>
</Svg>
)
};