Using @neodx/svg
with Vue
This guide will walk you through the process of integrating @neodx/svg
with a Vue 3 project using Vite.
We don't provide any pre-made, ready-to-use components. Such a solution would be too limited and opinionated for user-specific needs.
Instead, we offer a detailed yet simple guide on how to create your own components.
WARNING
In every guide, we will use TypeScript, and Tailwind CSS, but you can use any other technologies.
All guides
Setup
- Install the necessary dependencies:
npm install @neodx/svg
# or
yarn add @neodx/svg
- Configure your
vite.config.ts
:
/// <reference types="vitest" />
import svg from '@neodx/svg/vite';
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [
vue(),
tsconfigPaths(),
svg({
output: 'public/sprites',
fileName: '{name}.{hash:8}.svg',
metadata: 'src/shared/ui/icon/sprite.gen.ts',
inputRoot: 'src/shared/ui/icon/assets',
resetColors: {
// 1. Prevent resetting colors for flags and logos
exclude: [/^flags/, /^logos/],
// 2. Replace all known colors with currentColor
replace: ['#000', '#eee', '#6C707E', '#313547'],
// 3. Replace unknown colors with a custom CSS variable
replaceUnknown: 'var(--icon-secondary-color)'
}
})
]
});
This configuration sets up the SVG plugin to generate sprites and metadata.
Creating the Icon Component
Let's create a Vue component that will render our SVG icons:
<script setup lang="ts">
import { computed, type SVGAttributes } from 'vue';
import { sprites, type SpritesMeta } from './sprite.gen';
import clsx from 'clsx';
/** Icon props extending SVG props and requiring specific icon name */
interface IconProps extends SVGAttributes {
name: IconName;
}
/** Represents all possible icon names as the `<sprite name>:<symbol name>` string */
export type IconName = {
[Key in keyof SpritesMeta]: `${Key}:${SpritesMeta[Key]}`;
}[keyof SpritesMeta];
const props = defineProps<IconProps>();
/** Safe wrapper for extracting icon metadata */
const getIconMeta = (name: IconName) => {
const [spriteName, iconName] = name.split(':');
const item = sprites.experimental_get(spriteName!, iconName!, config);
if (!item) {
// Prevents crashing when icon name is invalid by returning a default icon
console.error(`Icon "${name}" is not found in "${spriteName}" sprite`);
return sprites.experimental_get('general', 'help', config)!;
}
return item;
};
const config = {
baseUrl: '/sprites/'
};
const meta = computed(() => {
const meta = getIconMeta(props.name);
const { width, height } = meta.symbol;
return {
...meta,
className: clsx(
{
'icon-x': width > height,
'icon-y': width < height,
icon: width === height
},
props.class
)
};
});
</script>
<template>
<svg
v-bind="props"
:class="meta.className"
:viewBox="meta.symbol.viewBox"
focusable="false"
:aria-hidden="true"
>
<use :href="meta.href" />
</svg>
</template>
This component:
- Supports grouped sprites with generated file names
- Provides type-safe
IconName
for autocompletion and convenient usage - Autoscales based on the icon's aspect ratio
Styling
Add the following CSS (example for Tailwind) to your project (e.g., in index.css
):
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/** Multi-color icons will use this variable as an additional color */
--icon-secondary-color: currentColor;
}
}
@layer components {
/*
Our base class for icons inherits the current text color and applies common styles.
We're using a specific component class to prevent potential style conflicts and utilize the [data-axis] attribute.
*/
.icon,
.icon-x,
.icon-y {
@apply select-none fill-current inline-block text-inherit box-content;
/** We need to align icons to the baseline, -0.125em is the 1/8 of the icon height */
vertical-align: -0.125em;
}
/* Set icon size to 1em based on its aspect ratio, so we can use `font-size` to scale it */
.icon,
.icon-x {
/* scale horizontally */
@apply w-[1em];
}
.icon,
.icon-y {
/* scale vertically */
@apply h-[1em];
}
}
This CSS sets up the base styles for the icons and handles the scaling based on the icon's aspect ratio.
Usage
Now you can use the Icon
component in your Vue application:
<script setup lang="ts">
import Icon from '../shared/ui/icon/icon.vue';
</script>
<template>
<div>
<Icon name="general:groups" class="text-xs" />
<Icon name="general:groups" />
<Icon name="general:groups" class="text-2xl" />
<Icon name="general:groups" class="text-4xl" />
<Icon name="general:groups" class="text-6xl" />
<div class="flex gap-4 items-center">
<Icon name="general:copy" class="text-xl" />
<Icon
name="general:edit"
class="bg-pink-100 text-pink-700 p-2 rounded-full border border-pink-700"
/>
</div>
<span class="text-sm inline-flex items-center gap-2">
<Icon name="general:filter" />
Small description example
<Icon name="tool:history" />
</span>
</div>
</template>
This example demonstrates various ways to use the Icon
component, including different sizes and custom styling.
Conclusion
By following this guide, you've successfully integrated @neodx/svg
with your Vue 3 project. The Icon
component you've created is flexible, type-safe, and easy to use throughout your application. Remember to adjust the paths and imports according to your project structure.