Skip to content

Named icons

Named icons must be implemented on your side using import.meta.glob() or, given your circumstances, another method.

Named icons are not present in the integrations because glob imports don’t support variable interpolation. See: https://vitejs.dev/guide/features#glob-import.

The demo below presents an example implementation of named icons.

Rendered demo
Demo's source code
src
assets
icons
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M21 5v6.59l-3-3.01l-4 4.01l-4-4l-4 4l-3-3.01V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2m-3 6.42l3 3.01V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-6.58l3 2.99l4-4l4 4"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M6 2v6l4 4l-4 4v6h12v-6l-4-4l4-4V2z"/></svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.28003 22C8.00316 22 9.40003 20.6031 9.40003 18.88C9.40003 17.1569 8.00316 15.76 6.28003 15.76C4.55691 15.76 3.16003 17.1569 3.16003 18.88C3.16003 20.6031 4.55691 22 6.28003 22Z" stroke="red" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M20.84 16.8001V4.60009C20.84 2.00009 19.21 1.64009 17.56 2.09009L11.32 3.79009C10.18 4.10009 9.40002 5.00009 9.40002 6.30009V8.47009V9.93009V18.8701" stroke="red" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.72 19.9199C19.4431 19.9199 20.84 18.5231 20.84 16.7999C20.84 15.0768 19.4431 13.6799 17.72 13.6799C15.9968 13.6799 14.6 15.0768 14.6 16.7999C14.6 18.5231 15.9968 19.9199 17.72 19.9199Z" stroke="red" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.40002 9.5199L20.84 6.3999" stroke="red" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.73 3.51001L15.49 7.03001C15.73 7.52002 16.37 7.99001 16.91 8.08001L20.1 8.61001C22.14 8.95001 22.62 10.43 21.15 11.89L18.67 14.37C18.25 14.79 18.02 15.6 18.15 16.18L18.86 19.25C19.42 21.68 18.13 22.62 15.98 21.35L12.99 19.58C12.45 19.26 11.56 19.26 11.01 19.58L8.01997 21.35C5.87997 22.62 4.57997 21.67 5.13997 19.25L5.84997 16.18C5.97997 15.6 5.74997 14.79 5.32997 14.37L2.84997 11.89C1.38997 10.43 1.85997 8.95001 3.89997 8.61001L7.08997 8.08001C7.61997 7.99001 8.25997 7.52002 8.49997 7.03001L10.26 3.51001C11.22 1.60001 12.78 1.60001 13.73 3.51001Z" stroke="green" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.53 20.4201H6.21C3.05 20.4201 2 18.3201 2 16.2101V7.79008C2 4.63008 3.05 3.58008 6.21 3.58008H12.53C15.69 3.58008 16.74 4.63008 16.74 7.79008V16.2101C16.74 19.3701 15.68 20.4201 12.53 20.4201Z" stroke="#7272ff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.52 17.0999L16.74 15.1499V8.83989L19.52 6.88989C20.88 5.93989 22 6.51989 22 8.18989V15.8099C22 17.4799 20.88 18.0599 19.52 17.0999Z" stroke="#7272ff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.5 11C12.3284 11 13 10.3284 13 9.5C13 8.67157 12.3284 8 11.5 8C10.6716 8 10 8.67157 10 9.5C10 10.3284 10.6716 11 11.5 11Z" stroke="#7272ff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
import { NamedIcon } from "@/NamedIcon.tsx";
export default function App() {
return (
<div className="images">
<NamedIcon
name="music"
color="orange"
className="image"
/>
<NamedIcon
name="star"
color="orange"
className="image"
/>
<NamedIcon
name="video"
color="orange"
className="image"
/>
</div>
);
}
import loadingIcon from "@/assets/icons/hourglass.svg";
import errorIcon from "@/assets/icons/broken-image.svg";
import { useEffect, useState } from "react";
import { SvgIcon, type SvgIconProps } from "vite-awesome-svg-loader/react-integration";
// Use same props as SvgIcon, but replace src with name
export interface NamedIconProps extends Omit<SvgIconProps, "src"> {
name: string;
}
// See: https://vitejs.dev/guide/features#glob-import
const rawIcons: any = import.meta.glob("/src/assets/icons/*.svg", {
// Put URL here or setup your imports via vite-awesome-svg-loader configuration
query: "?preserve-line-width&set-current-color",
});
// Transform keys from paths to file-based icon names
const icons: any = {};
for (const path in rawIcons) {
let name = path.split("/").pop() || "";
name = name.substring(0, name.lastIndexOf("."));
icons[name] = rawIcons[path];
}
export function NamedIcon({ name, ...iconProps }: NamedIconProps) {
const [src, setSrc] = useState(loadingIcon); // SVG source code
const onNameChange = async (name: string) => {
let code = errorIcon;
try {
code = (await icons[name]()).default; // Fetch SVG source code
} catch (e) {
console.error(e);
code = errorIcon; // Provide a fallback for when icon could not be loaded
}
// Verify that name hasn't changed. If it didn't, set its source code. Otherwise other onNameChange()
// call will handle the changes.
if (name === name) {
setSrc(code);
}
};
// Fetch new icon whenever name changes
useEffect(() => {
onNameChange(name);
}, [name]);
// Fetch initial icon
onNameChange(name);
return (
<SvgIcon
{...iconProps}
src={src}
/>
);
}
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
ReactDOM.createRoot(document.getElementById("app")!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
/// <reference types="vite/client" />
// Link some of the import configs. Unfortunately, it's impossible to fully type it. Use plugin configuration instead.
// Use import config when you really need it to avoid @ts-ignore before every import.
/// <reference types="vite-awesome-svg-loader" />
// Internal types
/// <reference types="vite-file-tree-builder" />
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1"
/>
</head>
<body>
<div id="app"></div>
<script
type="module"
src="/src/main.tsx"
></script>
</body>
</html>
{
"name": "react-named-icons",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "npm run build -- --watch",
"build": "npm run type-check && npm run build-only",
"build-only": "vite build --config vite.config.lib.ts",
"type-check": "tsc --noEmit",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
},
"dependencies": {
"vite-awesome-svg-loader": "*",
"vite-file-tree-builder": "*"
}
}
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }],
}
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// Import vite-awesome-svg-loader
import { viteAwesomeSvgLoader } from "vite-awesome-svg-loader";
export default defineConfig({
plugins: [react(), viteAwesomeSvgLoader({ urlImportsInLibraryMode: "emit-files" })],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});
Please open file to view its content