1

I need to include in my app some icons that the designer passed me in svg format. I use:

  • vue 3.4 (using composition api)
  • Vite 5.2
  • PrimeVue 4.0

I created a directory src/icons and put inside it my icon as a file:

src/icons/icon-text.svg

<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <g id="Icons"> <path id="Union" fill-rule="evenodd" clip-rule="evenodd" d="M0.383259 0C0.171591 0 0 0.159488 0 0.356227V6.20405C0 6.40078 0.171591 6.56027 0.383259 6.56027H1.65261C1.86428 6.56027 2.03587 6.40078 2.03587 6.20405V1.78968H8.98206V18.2103H5.69523C5.48356 18.2103 5.31197 18.3698 5.31197 18.5665V19.6438C5.31197 19.8405 5.48356 20 5.69523 20H14.2634C14.475 20 14.6466 19.8405 14.6466 19.6438V18.5665C14.6466 18.3698 14.475 18.2103 14.2634 18.2103H11.0041V1.78968H17.9779V6.20405C17.9779 6.40078 18.1495 6.56027 18.3612 6.56027H19.6167C19.8284 6.56027 20 6.40078 20 6.20405V0.356227C20 0.159488 19.8284 0 19.6167 0H0.383259Z" fill="black"/> </g> </svg> 

I tried several approaches:

  • Importing file content as is and use v-html in order to render svg
  • Importing file as a component (import TextIcon from '@/icons/icon-text.svg';) and then use it (<TextIcon />) )
  • Create a new component Icons.vue in order to import all icons

None of these approaches worked for me, I guess it shouldn't be so complicated, any help please?

BTW, if I directly put the svg code in a component, I do see it.

1
  • What is Vite config? Consider debugging what TextIcon is, this should give a good idea how it's supposed to be used. There could be several options for this, and Vite docs aren't specific about that. Commented Aug 12, 2024 at 18:33

1 Answer 1

4

Place the svg into a .vue file, wrapped in a <template /> tag and use it as any other vue component. Working demo. It doesn't need <script/> or <style/> tags.

If you have multiple icons, rather than having a .vue file for each, store them as:

export const icons = { Union: [ { d: 'M0.383259 0C0.171591 0 0 0.159488 0 0.356227V6.20405C0 6.40078 0.171591 6.56027 0.383259 6.56027H1.65261C1.86428 6.56027 2.03587 6.40078 2.03587 6.20405V1.78968H8.98206V18.2103H5.69523C5.48356 18.2103 5.31197 18.3698 5.31197 18.5665V19.6438C5.31197 19.8405 5.48356 20 5.69523 20H14.2634C14.475 20 14.6466 19.8405 14.6466 19.6438V18.5665C14.6466 18.3698 14.475 18.2103 14.2634 18.2103H11.0041V1.78968H17.9779V6.20405C17.9779 6.40078 18.1495 6.56027 18.3612 6.56027H19.6167C19.8284 6.56027 20 6.40078 20 6.20405V0.356227C20 0.159488 19.8284 0 19.6167 0H0.383259Z', fill: 'black', fillRule: 'evenodd', clipRule: 'evenodd' } ] } 

and use a single <CustomIcon icon="foo" />, where the Custom icon component would read the paths (and/or any other attributes from the icons object) and apply them to the <path />(s) inside the template (after all, this is a Vue component and it can leverage the power of Vue's DOM rendering capabilities) e.g:

<script setup> import { icons } from 'path/to/icons' defineProps({ icon: String }) </script> <template> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <path v-for="(props, key) in icons[icon]" :key="key" v-bind="props" /> </svg> </template> 

And use it as:

<CustomIcon icon="Union" /> 

Another solution is to use vue-inline-svg, along these lines:

<script setup> import InlineSvg from 'vue-inline-svg' const foo = new URL('@/assets/foo.svg', import.meta.url).href </script> <template> <inline-svg :src="foo" /> </template> 

Again, if you have more than one, you could store them as

export const icons = Object.assign( {}, ...['foo', 'bar'].map((name) => ({ [name]: new Url(`@/assets/${name}.svg`, import.meta.url).href })) ) 

And consume them as:

<script setup> import { icons } from './path/to/icons/file' defineProps({ icon: String }) </script> <template> <inline-svg :src="icons[icon]" /> </template> 

Note: vue-inline-svg was designed for a different purpose: replacing usages of <img src="some.svg" /> with the actual contents of the svg file.
Why would anyone that? There are many reasons for/against inlining svgs. But some things (e.g: changing color on hover, or using currentColor in fill or stroke attributes) are only possible on actual <svg /> elements and won't work on <img /> tags.

Antoher note: I don't endorse vue-inline-svg package and I'm not affiliated with it (but I've used it a few times). There might be other similar ones out there. Feel free to research.

Sign up to request clarification or add additional context in comments.

4 Comments

The first solution is very elegant, thank you! There is one problem, the line <path v-for="(props, key) in icons[icon]" :key="key" v-bind="props" /> doesn't work for me, I understand what you are trying to achieve but I get an error Uncaught (in promise) DOMException: Failed to execute 'setAttribute' on 'Element': '0' is not a valid attribute name.
I also had to use icons[props.icon] instead of icons[icon], but I don't think this is the problem.
I temporaly used this code: <path :d="icons[props.icon]['d']" :fill="icons[props.icon]['fill']" :fill-rule="icons[props.icon]['fillRule']" :clip-rule="icons[props.icon]['clipRule']"/>, will be happy to replace it to something more elegant...
You don't need the props. prefix inside <template> (vue automatically exposes all props to the template). And if it's key of icons, <path v-bind="icons[icon]"> will do. Working example.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.