SVG to Vue: fallthrough, props and the single-root rule
Converting SVG to a Vue component is mostly a non-event, and that surprises people coming from React: no attribute renaming, no style-object rewriting, no spread to remember. Vue templates accept SVG markup as written, and a single-root component forwards whatever the parent passes. The real work is knowing exactly where that convenience ends — because it ends in three specific places.
1. Almost nothing needs rewriting
Vue templates are HTML-compatible, so stroke-width stays stroke-width, class stays class, and a style string stays a string. The conversion is wrapping, not translating:
<!-- Icon.vue --> <template> <svg viewBox="0 0 24 24" fill="currentColor">…</svg> </template>
That is the entire component. The SVG to Vue tool does exactly this — it deliberately leaves the markup as the file contained it, because every rewrite is a chance to change what renders. Compare the React conversion, where camelCasing and the style object are unavoidable.
2. Fallthrough does what a React spread does
A Vue component with a single root element forwards every non-prop attribute the parent passes onto that root — class, style, aria-*, event listeners, width. No code in the component asks for this; it is the default.
<Icon class="nav__icon" aria-hidden="true" /> <!-- renders as --> <svg class="nav__icon" aria-hidden="true" viewBox="0 0 24 24" fill="currentColor">
The merge rules are the part worth memorising: class and style combine with what the file already has; any other attribute the caller passes wins over the file's value. That last rule is what makes the component behave like a native element — pass width="16" and you get 16.
3. The three places fallthrough stops
- A second root element. Fallthrough needs one unambiguous target. Wrap the svg in a
<div>— or add a sibling — and Vue no longer knows where the parent's attributes belong, warns, and drops them. Fix: bind them yourself withv-bind="$attrs"on the element you mean. - Wrapping the svg in an interactive element. An icon-button component wants the caller's
aria-labelon the<button>, not the svg. SetinheritAttrs: falseand placev-bind="$attrs"deliberately — which is also the accessible shape: name the control, hide the graphic (see accessible icons). - Declared props. The moment you declare
accentas a prop, it stops falling through — props are consumed, attributes pass by. Declare exactly what you intend to intercept and nothing more.
4. Dynamic fills: currentColor first, props second
Most icons need no prop at all: fill="currentColor" makes the svg follow the text colour of wherever it lands, so hover, focus and dark mode come from CSS you already wrote — the mechanics are in how to change SVG colors with CSS. Reach for a prop only when a second, independent tone must vary per use:
<script setup lang="ts"> withDefaults(defineProps<{ accent?: string }>(), { accent: '#2f6df6' }) </script> <path :fill="accent" d="…" />
Keep currentColor for the primary shape and drive only the accent from the prop — one prop, not one per colour, or the component grows an API nobody wanted. The Color Replacer converts a file's fills to currentColor in one pass before you convert it.
5. The id collision follows you from React
Gradient, mask and clipPath ids are global to the rendered document, in Vue exactly as everywhere else: mount two icons that both define id="gradient-1" and both paint with whichever definition the browser saw first. Two fixes, in order of preference: run the file through the Optimizer first, which strips ids nothing references — most collisions are between ids nobody uses — and for defs the file genuinely needs, generate per-instance ids with Vue 3.5's useId() and reference them as `url(#${id})`. The failure mode and its diagnosis are covered in the React guide — the fix translates verbatim.
6. When a component is the wrong container
A component earns its place when the icon needs to inherit colour, take dynamic props, or appear in dozens of places. Decorative artwork that never changes is better as a plain <img> or a CSS background — no bundle weight, browser-cached — accepting that CSS cannot reach inside it (the embedding rules are in the colour guide). Icon sets have a third option: one sprite of symbols referenced by <use>, which keeps the markup in one cacheable file while inherited colour still crosses the boundary.