react-dynamic-svg-icon
v1.0.0
Published
A lightweight, cross-platform dynamic SVG icon wrapper for React Web, Expo, and React Native CLI.
Maintainers
Readme
⚛️ react-dynamic-svg-icon
A lightweight, zero-friction, cross-platform dynamic SVG icon wrapper for React Web (Vite), Expo, and React Native CLI.
Building cross-platform apps often means fighting with SVG configurations. Web needs vite-plugin-svgr and CSS color properties. React Native needs react-native-svg-transformer and explicit color props.
react-dynamic-svg-icon solves this. It provides a single, unified <DynamicIcon> component that normalizes props across all platforms, and includes 1-liner configuration wrappers so you never have to write complex bundler boilerplate again.
✨ Features
- Write Once, Run Anywhere: Use the exact same icon component on Web, iOS, and Android.
- Unified Sizing: Pass a single
size={24}prop instead of jugglingwidthandheight. - Smart Styling: Automatically maps the
colorprop to Web CSS or React Native props safely. - Test Ready: Pass
testID="my-icon"and it automatically maps todata-testidon the web andtestIDon native. - Zero Boilerplate: Includes built-in plugins for Vite and Metro to handle SVGO optimization safely (preserves your masks and gradients!).
📦 Installation
Install the package and its peer dependencies based on your environment.
npm install react-dynamic-svg-iconFor Web (Vite):
npm install -D vite-plugin-svgrFor React Native / Expo:
npm install react-native-svg
npm install -D react-native-svg-transformer⚙️ Configuration (1-Line Setup)
We handle the heavy lifting for your bundler so you don't have to.
Web (Vite)
Open your vite.config.ts and add the dynamicSvgPlugin:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { dynamicSvgPlugin } from 'react-dynamic-svg-icon/vite';
export default defineConfig({
plugins: [
react(),
dynamicSvgPlugin() // Automatically configures SVGR safely!
],
});
React Native / Expo (Metro)
Open your metro.config.js and wrap your default config with withDynamicSvg.
Standard Setup:
const { getDefaultConfig } = require("expo/metro-config"); // or '@react-native/metro-config'
const { withDynamicSvg } = require('react-dynamic-svg-icon/metro');
const config = getDefaultConfig(__dirname);
module.exports = withDynamicSvg(config);Advanced Setup (Chaining with NativeWind / Reanimated):
If you are using other tools that require Metro wrappers (like NativeWind), simply chain them by reassigning the config object:
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require('nativewind/metro');
const { withDynamicSvg } = require('react-dynamic-svg-icon/metro');
let config = getDefaultConfig(__dirname);
// 1. Apply NativeWind
config = withNativeWind(config, { input: './global.css' });
// 2. Apply Dynamic SVG
config = withDynamicSvg(config);
module.exports = config;🛠️ The "Golden Rule" for SVG Files
For this package to dynamically change the colors of your icons, your raw .svg files must use currentColor instead of hardcoded hex codes.
- ❌ Bad (Hardcoded):
<path fill="#FF0000" d="..." /> - ✅ Good (Dynamic):
<path fill="currentColor" d="..." />
Pro-Tip: When exporting icons from Figma, change your icon color to black (
#000000), export the SVG, and do a quick "Find and Replace" in your code editor to swap#000000withcurrentColor.
🚀 Usage
Example 1: Web (React + Tailwind)
import React, { useState } from 'react';
import HomeIcon from './assets/home.svg';
import { DynamicIcon } from 'react-dynamic-svg-icon';
export default function App() {
const [isActive, setIsActive] = useState(false);
return (
<button onClick={() => setIsActive(!isActive)}>
<DynamicIcon
Icon={HomeIcon}
size={32}
color={isActive ? "blue" : "gray"}
strokeWidth={isActive ? 2 : 1}
className="hover:scale-110 transition-transform"
/>
</button>
);
}Example 2: React Native / Expo
import React, { useState } from 'react';
import { TouchableOpacity, View } from 'react-native';
import HomeIcon from './assets/home.svg';
import { DynamicIcon } from 'react-dynamic-svg-icon';
export default function MobileApp() {
const [isActive, setIsActive] = useState(false);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<TouchableOpacity onPress={() => setIsActive(!isActive)}>
<DynamicIcon
Icon={HomeIcon}
size={40}
color={isActive ? "#3B82F6" : "#9CA3AF"}
testID="home-tab-icon"
/>
</TouchableOpacity>
</View>
);
}📖 Props API
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| Icon (Required) | ComponentType | undefined | The imported .svg asset. |
| size | number \| string | 24 | Unified shortcut applied to both width and height. |
| color | string | undefined | Global color override. Drives the currentColor value in the SVG. |
| fill | string | undefined | Explicit fill color. Overrides the SVG's internal fill. |
| stroke | string | undefined | Explicit stroke color. Overrides the SVG's internal stroke. |
| strokeWidth| number \| string | undefined | Thickness of the stroke paths. |
| opacity | number \| string | undefined | Global opacity of the icon (0 to 1). |
| className | string | undefined | Utility class string (e.g., Tailwind). Safely ignored on Native. |
| style | CSSProperties | undefined | Inline styles. Accepts Web CSS and React Native style objects. |
| testID | string | undefined | Testing identifier. Auto-maps to data-testid on Web and testID on Native. |
(Supports all other standard SVG props like strokeLinecap, strokeLinejoin, fillOpacity, etc.)
⚠️ Troubleshooting
My icon color isn't changing!
- Ensure your
.svgfile usescurrentColorinstead of a hardcoded hex code likefill="white". - Vite Cache (Web): If you just updated your SVG file, Vite might be caching the old version. Run your dev server with
npm run dev -- --forceor delete thenode_modules/.vitefolder to bust the cache. - Metro Cache (Native): Start your Expo/Native server with
npx expo start -cto clear the Metro bundler cache.
💖 Support the Project
If this package saved you hours of configuring Webpack, Vite, and Metro bundlers, consider supporting my open-source work!
License: MIT
