Frameworks

SVG Icons in Vue 3: The Complete Guide (lucide-vue-next, Iconify and unplugin-icons)

How to use SVG icons in Vue 3 and Nuxt: lucide-vue-next, @iconify/vue, unplugin-icons and hand-rolled components compared, with sizing, colour and accessibility patterns.

IconVault Team 16 Oct 2026 34 min read

There are four sane ways to use SVG icons in Vue 3, and the right one depends on how many icons you need and how much bundle discipline you want. This guide walks through each approach with the trade-offs stated plainly.

Choosing an approach in one paragraph each

Pick lucide-vue-next when your design language is Lucide and you want zero surprises: official components, predictable props, clean tree-shaking. Pick @iconify/vue when you genuinely need several sets or are prototyping and want every glyph reachable by name today. Pick unplugin-icons when you want that name-based ergonomics with compile-time guarantees and no runtime. Pick local components when the set is small and stable and you would rather own twenty files than one dependency. The sections below give each option a fair hearing, then cover the Nuxt-specific module, shared patterns for sizing and colour, performance behaviour, the errors everyone hits, and migration advice for teams leaving icon fonts behind.

Option 1: lucide-vue-next

If your design system is Lucide, lucide-vue-next is the official Vue package: one component per icon, props for size and stroke-width, and clean tree-shaking. It mirrors the React experience almost exactly, which helps teams running both frameworks.

npm install lucide-vue-next
<script setup>
import { Search, Settings } from "lucide-vue-next";
</script>

<template>
  <Search :size="20" />
</template>

Three details make lucide-vue-next pleasant in real projects. The size prop takes a number or string and defaults to 24, the stroke-width prop lets you soften icons in large display contexts, and every component forwards class and attributes to the rendered svg, so class="text-primary" or a Tailwind size utility behaves the way Vue developers expect. Because each icon is an individual ES module export, Vite tree-shakes unused glyphs automatically in production builds. One caution: the package is the Vue 3 line of the project formerly known as vue-lucide and similar names; install lucide-vue-next exactly, since lookalike packages are stale.

Option 2: @iconify/vue for every set at once

Iconify's Vue component can render any icon from any open-source set by name, including all 239 sets indexed on IconVault. It is the fastest way to prototype with mixed sets, but production apps should bundle icon data locally rather than depending on runtime API calls. Pick your finalists from the set directory and lock in one family.

npm install @iconify/vue

<script setup>
import { Icon } from "@iconify/vue";
</script>

<template>
  <Icon icon="lucide:search" width="20" />
</template>

Making Iconify offline-safe

Out of the box, @iconify/vue fetches icon data from the Iconify API the first time an icon renders. That is fine for a prototype and wrong for production: it adds a network dependency, can flash empty space during server-side rendering, and breaks behind strict content security policies or in offline-first apps. The fix is to ship the data yourself. Install the @iconify-json package for your set, register it once at startup with addCollection, and the component resolves names locally with no requests at all.

import { addCollection } from "@iconify/vue";
import lucide from "@iconify-json/lucide/icons.json";

addCollection(lucide);

Registering a whole set still beats fetching, but the data file for a large family can be sizeable. If you use only a few dozen glyphs, register individual icons with addIcon and import their data objects from the set's JSON, or switch those slots to local components. The library browser shows per-set icon counts, which helps you predict data weight before you commit to a family.

Option 3: unplugin-icons for compile-time imports

unplugin-icons turns icon names into components at build time (for example ~icons/lucide/search) and works with Vite and Nuxt. You get auto-import ergonomics with zero runtime cost, at the price of one more build plugin to maintain.

// vite.config.ts
import Icons from "unplugin-icons/vite";

export default defineConfig({
  plugins: [vue(), Icons({ autoInstall: false })],
});

// Usage anywhere, no import statement needed with a resolver
// or import it explicitly:
import IconSearch from "~icons/lucide/search";

Two features make unplugin-icons more than a convenience. First, the Components resolver from unplugin-vue-components can auto-import icons by component name, so templates stay free of icon import lists entirely. Second, custom collections let you register a folder of your own SVG files and use them with the same syntax as library icons, which unifies brand glyphs and library glyphs behind one pattern. Remember to generate or commit the TypeScript declarations for the virtual ~icons modules; without them the build works but the editor complains, and teams tend to 'fix' that by disabling type checks, which is the real cost.

Option 4: local SVG components

For a small, stable icon set, nothing beats copying the SVG into a local component with currentColor and a size prop. Generate the component from any icon using IconVault's copy-as-Vue output on the browse page, or convert files in bulk with the SVG to JSX and component tools. No dependency, no surprises, full control.

<script setup>
defineProps({ size: { type: [Number, String], default: 20 } });
</script>

<template>
  <svg :width="size" :height="size" viewBox="0 0 24 24" fill="none"
       stroke="currentColor" stroke-width="2" aria-hidden="true">
    <circle cx="11" cy="11" r="7" />
    <path d="M21 21l-4.3-4.3" />
  </svg>
</template>

Local components reward a little standardisation. Give every one the same prop contract, size plus class passthrough, put them in components/icons, and re-export them from an index file. Call sites then read identically whether the glyph is local or from a package, and a future migration to lucide-vue-next or unplugin-icons becomes a mechanical exercise instead of an archaeological dig.

The Nuxt route: @nuxt/icon

Nuxt applications have a fifth path worth knowing. The @nuxt/icon module wraps Iconify with server-side rendering as a first-class concern: icons render on the server into the HTML, can be configured with local collections through @iconify-json packages, and fall back to a server endpoint for long-tail glyphs. For content-heavy Nuxt sites that pull from two or three sets, it removes most of the SSR friction described later in this guide. For a Nuxt app already standardised on Lucide, lucide-vue-next remains simpler and lighter. Modules are conveniences, not obligations, and the deciding factor should be how many sets you truly need.

Colour, size and accessibility in Vue

  • Drive colour with CSS color and currentColor, never a hard-coded fill, so dark mode just works.
  • Standardise on 16/20/24px sizes through a wrapper component instead of ad-hoc classes.
  • Add aria-hidden to decorative icons and real labels to icon-only buttons.
  • In Nuxt, keep icon components client-agnostic: plain SVG renders identically in SSR and hydration.

A wrapper component pays for itself

Whatever source you choose, route rendering through one AppIcon-style wrapper that owns the size scale, the default aria-hidden, and the set-specific quirks. The wrapper translates your design tokens, say name plus size token, into the right component or Iconify identifier. Templates then never mention package APIs, and the day you change libraries, only the wrapper changes. This pattern is doubly valuable in Vue because templates are compiled from strings in single-file components; a wrapper keeps icon policy in script land where TypeScript can check it.

<script setup>
import { Icon } from "@iconify/vue";
const props = defineProps({ name: String, size: { type: Number, default: 20 } });
</script>

<template>
  <Icon :icon="'lucide:' + props.name" :width="props.size" :height="props.size" aria-hidden="true" />
</template>

Theming with CSS variables

Because every approach above ultimately renders SVG with currentColor, Vue theming is CSS theming. Set color on the element or an ancestor and icons follow, including through Tailwind's text colour utilities and dark: variants. Status colours deserve the same semantic discipline as any framework: success, warning and error hues should come from tokens, not from a developer's memory of a hex value. Where an icon needs two tones, reach for a duotone weight from a set like Phosphor rather than hand-colouring paths, and keep the practice rare. Our guide to using SVG icons well covers the visual rules in framework-neutral terms.

Performance: what each approach actually costs

All four approaches can be fast, and all four can be sabotaged. lucide-vue-next and local components cost exactly the icons you import, tree-shaken. unplugin-icons compiles imports to the same place, with the added benefit that nothing icon-shaped exists at runtime beyond the components themselves. @iconify/vue has two cost profiles: API mode costs requests and layout stability, while bundled-data mode costs the size of the data you register, which is why registering per icon matters for big sets. The metric to watch is not the icon system in the abstract; it is the production bundle report for your app. Weigh it after any change, the same advice as the site speed and SVG guide gives for static sites.

Common errors and fixes

The icon renders as an empty box or nothing at all

With @iconify/vue this is usually a name problem: the prefix or glyph name is misspelled, or the data for that set was never registered and the API fetch failed silently. Check the name against the set's listing on the browse page, confirm addCollection ran before first render, and look for a failed request in the network tab. With package components, an empty box almost always means the import resolved to a component that rendered an svg with no paths, which happens when a stale lookalike package is installed instead of the official one.

Failed to resolve component

Vue raises this when a template references a component that was neither imported nor globally registered. With unplugin-icons plus the Components resolver, the cause is a missing resolver entry in the Vite config rather than a missing icon. With manual imports in script setup, check that the name in the template matches the imported binding exactly, including casing, since compiled templates are case-sensitive about component resolution.

Icons flash or swap after hydration in Nuxt

A flash means the server and the client disagreed. API-mode Iconify renders nothing on the server and the icon after the fetch, so SSR HTML and the hydrated DOM differ. Bundling the collection data, or using @nuxt/icon with local collections, removes the fetch and the disagreement together. Local components and lucide-vue-next never have this problem because their SVG exists at build time.

TypeScript complains about ~icons modules

Virtual modules need declarations. unplugin-icons generates a d.ts when configured, and unplugin-vue-components writes components.d.ts for auto-imported components. Commit those generated files or regenerate them in your prepare script; do not silence the errors globally, because the same configuration gap will hide real component typos elsewhere.

Migrating from icon fonts and vue-fontawesome

Teams often arrive at SVG icons in Vue 3 carrying an icon font or the Font Awesome Vue component from a Vue 2 era codebase. Migrate in place, screen by screen: replace each font class or fa-icon usage with the component equivalent at the same visual size, then remove the font pipeline entirely, because a leftover webfont that loads 'just in case' taxes every page. Expect small optical differences; Font Awesome's solid style has no exact outline counterpart, so choose replacements by meaning and preview candidates in the icon set libraries rather than trusting name matches. Once the font is gone, you also delete a class of problems: no more FOUT swaps, no more private-use-area codepoints, and screen readers stop announcing font glyphs as punctuation.

Whichever route you take for SVG icons in Vue 3, consistency of the underlying set matters more than the integration method. Compare candidate sets in our free SVG icon libraries roundup before you wire anything up.

Server rendering in depth: what Nuxt actually sends

It is worth understanding what leaves the server in each approach, because icon bugs in Nuxt are usually payload misunderstandings. With lucide-vue-next, unplugin-icons or local components, the server renders finished svg markup into the HTML and hydration finds identical DOM waiting for it. With @iconify/vue in API mode, the server renders a placeholder or nothing, the client fetches data after mount, and the icon pops in; that pop is both a visual defect and a potential layout shift if the slot had no reserved size. With bundled Iconify data, rendering is again fully server-side, at the cost of the registered data travelling in your JavaScript payload for hydration. Reserve explicit width and height on every icon slot regardless of approach, and the remaining differences between these modes become a question of bytes rather than of visible glitches. This is also why icon-heavy Nuxt layouts deserve a view-source check during development: ten seconds reading the server HTML tells you which mode you are really in.

Sprites and symbol definitions in Vue

Vue can use the same SVG sprite technique as any stack: define symbols once in a component mounted at the app root, then reference them with use elements anywhere. This suits very large applications where the same twenty glyphs repeat across hundreds of component instances, because the artwork is defined once per document instead of inlined per usage. A SpriteDefs component holding the symbol set, plus a tiny Icon component that takes a name and size, gives a clean call site:

<script setup>
defineProps({ name: String, size: { type: Number, default: 20 } });
</script>

<template>
  <svg :width="size" :height="size" fill="currentColor" aria-hidden="true">
    <use :href="'#' + name" />
  </svg>
</template>

The trade-offs mirror sprites everywhere: per-instance styling is limited to inherited properties, multi-colour glyphs do not belong in the set, and a missing symbol fails silently as an empty box. Component-based approaches fail louder and style better, which is why sprites are a scaling optimisation to adopt when repetition is measured, not a default. If you adopt one, generate the symbol file from your source icons rather than maintaining it by hand, and keep the list of defined symbols in the same review as the components that reference them.

Using several sets without visual chaos

Iconify makes it technically trivial to pull a glyph from any of 239 sets in the same template, and that freedom is the most common way Vue apps end up visually incoherent. The sets differ in grid, corner radius, stroke logic and optical size, and mixing them at 20px reads as carelessness even when each icon is individually good. If your product genuinely needs two families, assign them territories with different jobs: one for interface glyphs, one for brand and social marks, for example, and never two interface families side by side in the same toolbar. Document the territories where the wrapper component lives, and treat a cross-territory import in review the way you would treat a random colour. The discipline matters more than the specific sets chosen, and IconVault's category browsing can help you find a single family with the breadth to make the second one unnecessary.

Publishing Vue component libraries with icons

If you ship a Vue component library, icons become an API design problem. Bundling a specific icon package into your library forces it on consumers and can duplicate the set in their bundles when they use the same package at a different version. The friendlier patterns are to accept icons as slots or component props, so consumers render your button chrome around their own icon system, or to depend on the icon package as a peer dependency with a declared range. Document which you chose, because icon duplication is the kind of bloat consumers notice in their bundle reports and attribute to your library even when their own setup caused it. For internal design systems the stakes are lower, but the slot pattern still wins: it keeps the icon decision at the application layer, where the one-set rule can actually be enforced.

Testing that icons render

Icon failures are silent, so a little test coverage pays disproportionately. At minimum, snapshot or screenshot the handful of screens dense with iconography, navigation, toolbars and empty states, and let visual regression flag the empty boxes. In unit tests for shared components, assert that an icon-only button exposes its accessible name, which catches the common regression where a refactor drops the label but keeps the glyph. For apps using bundled Iconify data, one smoke test that renders a page server-side and checks for svg elements in the HTML catches the entire class of registration and hydration bugs before users do. None of this requires icon-specific tooling; it requires remembering that icons are UI, and UI that renders as nothing is a bug your type checker cannot see.

Pinning versions across a Vue workspace

Icon packages rename glyphs occasionally, and in a monorepo with a Nuxt app, a Storybook and a component library, drifting versions produce the maddening bug where the same name renders in one workspace and not another. Pin icon dependencies at the workspace root where your package manager allows it, upgrade them in one dedicated change, and grep for renamed glyphs against the new release notes. The same discipline applies to @iconify-json data packages, whose identifiers must match what the runtime component expects. Boring version hygiene is what keeps an icon system boring, and boring is the goal.

Scoped styles, deep selectors and icon colour

One Vue-specific papercut deserves a mention: scoped styles in single-file components do not automatically reach into child components, and icon components are children. A rule like .toolbar svg in a scoped block will not recolour an icon rendered by a child component, which surprises developers who expect global CSS behaviour. The clean solutions are to pass colour as a class or style on the icon component itself, since attribute fallthrough puts your class on the rendered svg, or to use a :deep() selector deliberately for the rare container-level override. Passing presentation at the call site is the better habit, because it keeps the wrapper pattern intact and survives refactors that swap the underlying icon source.

Choosing an approach for your app size

Match the approach to the app, not to the trend. A landing page with twenty glyphs and a dashboard with two hundred have different needs, and Vue SVG icons should be chosen against the app in front of you rather than the approach that is fashionable this month. Small apps are best served by local components or lucide-vue-next, because both keep the dependency surface tiny and the behaviour obvious. When developers ask how to use SVG icons in Vue 3 for a large product, the answer shifts toward unplugin-icons or @nuxt/icon, where build-time resolution and server rendering justify the extra configuration. Vue SVG icons chosen this way stay cheap at every scale, because the approach grows with the app instead of fighting it.

Count sets before you count icons. The decision that matters most for Vue SVG icons is how many families the product genuinely needs, since one family with any integration beats three families with the best integration. If the answer is one, lucide-vue-next is almost always the right call, and the remaining work is wrapper discipline rather than tooling. If the answer is genuinely two or three, for example interface glyphs plus brand marks, then the question of how to use SVG icons in Vue 3 becomes a question of boundaries: which set owns which territory, documented where the wrapper lives. Vue SVG icons with documented territories survive redesigns, while undocumented mixes quietly become the visual debt the next team inherits.

Naming conventions in Vue templates

One naming rule prevents a hundred template bugs. Vue templates resolve components by name, so Vue SVG icons need a naming convention that survives auto-import, manual import and code review alike. The reliable habit is to use the library's PascalCase component name in script setup and the same name in the template, and to give local glyphs the same treatment with an Icon suffix, so SearchIcon reads as a component everywhere it appears. When teams document how to use SVG icons in Vue 3, the naming rule deserves its own paragraph, because inconsistent names are the most common reason a template renders nothing while the build stays green. Vue SVG icons with consistent names are greppable, reviewable and refactorable, which is everything a growing codebase needs from them.

Keep user-facing names separate from component names. Settings screens and category managers often let users pick an icon, and the stored key should be a stable slug such as home or alerts, translated to a component in exactly one map, rather than a component name persisted in a database. This separation means Vue SVG icons can be renamed, swapped or restyled without migrating stored data, and it keeps user input out of the component resolution path entirely. The guide to how to use SVG icons in Vue 3 in admin tools should therefore start with the allow-list map pattern, since it is both the secure and the maintainable choice. Vue SVG icons behind a map also make testing trivial: render the map, assert every entry resolves, and no picker can ever reference a glyph that does not exist.

Icons in navigation, layouts and routing

Navigation icons carry the product's wayfinding. In a Vue Router layout, Vue SVG icons in the sidebar and top bar are the first thing users learn and the last thing they forgive when it changes, so navigation glyphs should be chosen from the core of the set and frozen early. Active states deserve a deliberate treatment: a filled variant, a colour change, or a weight shift, applied consistently across every nav item, rather than a different trick per section. When teams plan how to use SVG icons in Vue 3 navigation, they should prototype the full sidebar with real labels and real active states, because Vue SVG icons that look fine in isolation often crowd each other at 20px beside 14px text. Navigation is where icon discipline pays off most visibly, since users see it on every single screen.

Reserve slots for badges and indicators. Notification dots, counts and status pips sit on top of navigation Vue SVG icons, and they need reserved space and a positioning contract, or they will shift layout when they appear. The robust pattern is a relatively positioned wrapper with an absolutely positioned badge, sized in the same token scale as the icon, so the badge never moves the glyph beneath it. Document the badge sizes alongside the icon sizes in the wrapper, because a badge drawn at an ad-hoc size is the classic detail that makes an otherwise careful layout feel unfinished. For teams learning how to use SVG icons in Vue 3 at product scale, the badge contract is a small rule with an outsized effect on perceived polish, and Vue SVG icons with stable badges read as a finished system.

Dark mode and theming in Vue apps

Theme through CSS, never through icon props. Because every Vue SVG icons approach ultimately renders SVG with currentColor, theming is a CSS concern: set colour on the element or an ancestor and the glyph follows, including through Tailwind's dark variant. The failure to avoid is passing hard-coded colours as props to individual icons, which creates a second theming system that dark mode cannot reach. When documenting how to use SVG icons in Vue 3 with dark mode, the rule to state plainly is that no icon component accepts a colour prop at all; colour arrives from context, period. Vue SVG icons themed this way survive every future theme change, because there is exactly one place where colour is decided.

Semantic colours need tokens, not hex values. Success, warning and error glyphs should draw their colours from design tokens, so a rebrand or an accessibility-driven contrast fix propagates to every status icon at once. In Vue, the natural home for these tokens is the same CSS variable layer that drives the rest of the theme, consumed through Tailwind utilities or plain classes on the icon component. Teams that treat how to use SVG icons in Vue 3 as a token problem rather than a per-icon problem find that contrast audits become mechanical: change the token, screenshot the states, done. Vue SVG icons in status roles are also the ones most likely to be the sole carrier of meaning, so pair every status colour with a label or a shape difference, never colour alone.

Icons in tables, forms and dashboards

Dense Vue screens reward restraint. Dashboards pack Vue SVG icons into tables, filters, cards and charts, and the discipline that keeps them readable is a strict size scale: 16px for row actions, 20px for toolbar actions, 24px only for section headers. Anything larger in a dense screen competes with the data, and anything smaller than 16px turns to mush on standard displays. When planning how to use SVG icons in Vue 3 dashboards, prototype with real data density, because Vue SVG icons that look balanced in an empty Figma frame often feel noisy beside ten columns of numbers. The dashboard test is simple: if removing an icon makes the row clearer, the icon was decoration, and decoration has no place in dense UI.

Form icons should hint, never replace labels. A search glyph inside a filter input or a calendar glyph beside a date field helps users scan, but Vue SVG icons in forms must never carry meaning that the label does not, since screen readers and autofill users may never encounter the glyph. The accessible pattern is a visible label plus a decorative icon marked aria-hidden, with status icons in validation messages treated as reinforcements of text that already exists. For teams writing guidance on how to use SVG icons in Vue 3 forms, this distinction is the whole accessibility story: decorative glyphs are hidden, meaningful glyphs are labelled, and no glyph is the only signal. Vue SVG icons used this way make forms faster to scan for everyone without excluding anyone.

Animating Vue icons with transitions

Vue transitions make icon motion trivial to add and easy to overdo. The built-in Transition component can rotate a chevron, fade a spinner or morph a menu glyph with a few lines, and Vue SVG icons respond to these transitions naturally because they are ordinary components. The discipline is to animate state changes only: an accordion chevron rotating on open, a refresh glyph spinning during a fetch, a check drawing in on success. When teams explore how to use SVG icons in Vue 3 with motion, they should budget motion the way they budget colour, as a small set of approved transitions documented beside the icon wrapper. Vue SVG icons with approved motion feel alive; Vue SVG icons with improvised motion feel distracting, and the difference is entirely the documentation.

Always provide a reduced-motion path. Vue's transition system pairs naturally with the prefers-reduced-motion media query, and every icon animation should have a static equivalent that appears instantly when the user asks for less motion. The practical habit is to wrap motion classes in a media query or a Tailwind motion-safe variant, and to test the reduced-motion rendering of every animated icon, not just the page-level transitions. For developers documenting how to use SVG icons in Vue 3 accessibly, reduced motion belongs in the same checklist as aria labels, because motion sensitivity is as real as any other accessibility need. Vue SVG icons that respect the user's motion preference are a quiet signal that the product was built with care.

Testing icons with Vitest and visual regression

Icon failures are silent, so test for them explicitly. A renamed glyph, a missing Iconify registration or a broken auto-import renders as an empty box with no console error, which means Vue SVG icons need tests that a type checker cannot provide. The highest-value test is a visual regression pass over the screens densest with iconography: navigation, toolbars, empty states and dashboards. When teams decide how to use SVG icons in Vue 3 in a tested codebase, they should add a smoke test that server-renders a representative page and asserts svg elements exist in the HTML, catching registration and hydration bugs before users do. Vue SVG icons covered this way fail loudly in CI instead of silently in production.

Unit-test the wrapper, screenshot the rest. The AppIcon wrapper deserves unit tests: every supported name resolves, every size token maps to a real dimension, and icon-only buttons expose an accessible name. Beyond the wrapper, screenshot tests carry the weight, because icon bugs are visual by nature and a pixel diff catches what assertions cannot. Keep the screenshot set small and stable, a dozen screens at most, so the suite stays fast enough to run on every pull request. For guidance on how to use SVG icons in Vue 3 at team scale, the testing paragraph matters more than it seems: it is what lets a team change icon libraries later with confidence, since the screenshots prove the new set renders everywhere the old one did.

Sharing icons across a monorepo

A shared icon package is a design system decision. In a monorepo with a Nuxt app, a Storybook and a component library, Vue SVG icons should live in exactly one shared package, versioned and documented, rather than duplicated per app. The shared package exports the wrapper, the allow-list map and the local glyphs, and each app consumes it as a dependency, so an icon change ships everywhere at once. When architects plan how to use SVG icons in Vue 3 across workspaces, the versioning rule matters most: pin the icon package at the workspace root and upgrade it in one dedicated change, because drifting versions produce the maddening bug where a glyph renders in one app and not another. Vue SVG icons shared this way stay boring, and boring is the goal.

Peer dependencies keep consumers free. If the shared package bundles a specific icon library, consumers inherit that choice and can end up with two copies of the set when they use the same library at a different version. The friendlier pattern is to accept icons as slots or component props in shared components, so applications render library chrome around their own icon system, or to declare the icon package as a peer dependency with a stated range. Document the choice in the package readme, because bundle duplication is the kind of bloat consumers notice and blame on the library. For teams scaling how to use SVG icons in Vue 3 beyond one app, the slot pattern is what keeps the icon decision at the application layer, where the one-set rule can actually be enforced.

Accessibility deep-dive for icon buttons

An icon-only button is a button with a hidden label requirement. Every Vue SVG icons button without visible text needs an accessible name, delivered through an aria-label, an sr-only span, or a title that assistive technology actually exposes, and the label should name the action, not the glyph: Search products rather than Magnifying glass. The wrapper is the right place to enforce this, by requiring a label prop whenever no visible text is passed, so the rule cannot be forgotten at individual call sites. When writing standards for how to use SVG icons in Vue 3 accessibly, this requirement should be stated as a hard rule with examples, because icon-only buttons are the single most common accessibility failure in Vue applications. Vue SVG icons without labels are decoration; with labels, they are controls.

Focus visibility is part of the icon contract. An icon button that shows no focus ring is unusable by keyboard, no matter how perfect its label, so the wrapper or the button component must guarantee a visible focus state that survives theming. Test focus order and visibility with a keyboard alone, tabbing through navigation, toolbars and dialogs, and confirm that every Vue SVG icons control announces itself and shows where focus sits. For teams finalising how to use SVG icons in Vue 3, a keyboard pass over icon-heavy screens is a fifteen-minute check that catches the failures automated tools miss. Vue SVG icons that are labellable, focusable and motion-respectful are not a nice-to-have; they are the baseline for shipping to real users.

Icons in lists and transitions

TransitionGroup reveals icon mistakes instantly. For Vue SVG icons, list animations expose misaligned glyphs because moving rows draw the eye to every icon, and Vue SVG icons that share one size and stroke survive the motion while mixed sets look chaotic. Teams learning how to use SVG icons in Vue 3 lists should keep icon rendering inside the transitioning item component, because Vue SVG icons rendered outside it lag behind the animation. The guidance on how to use SVG icons in Vue 3 with transitions is simple: stable sizes, stable slots, and Vue SVG icons that never change identity mid-animation.

Keep list icons out of the transition payload. For Vue SVG icons, the animation should move the row while the icon rides along unchanged, and Vue SVG icons that re-render or swap during a transition cause flicker that users read as a bug. Teams practising how to use SVG icons in Vue 3 with TransitionGroup should key list items stably and avoid conditional icon swaps inside animated rows, because Vue SVG icons with unstable keys remount instead of moving. The rule for how to use SVG icons in Vue 3 in animated lists is that icons are passengers, never drivers, and Vue SVG icons treated as passengers animate flawlessly.

Responsive sizing for Vue icons

Breakpoints should change chrome, not glyphs. For Vue SVG icons, the glyph itself should stay the same across breakpoints while the surrounding layout adapts, and Vue SVG icons that change size per breakpoint create a product that feels different on every device. Teams planning how to use SVG icons in Vue 3 responsively should define size tokens per role rather than per screen, because Vue SVG icons sized by role stay consistent from phone to desktop. The advice on how to use SVG icons in Vue 3 at responsive scale is to audit the smallest viewport first, since Vue SVG icons that work at 360px work everywhere.

Hide actions behind menus instead of shrinking icons. For Vue SVG icons, a toolbar that shrinks glyphs below 16px on small screens sacrifices legibility for completeness, and Vue SVG icons hidden behind an overflow menu preserve both. Teams designing how to use SVG icons in Vue 3 for mobile should decide per toolbar which actions survive at each breakpoint, because Vue SVG icons without a responsive plan overflow or crowd. The practice for how to use SVG icons in Vue 3 on small screens is a documented collapse order, so Vue SVG icons degrade predictably instead of breaking the layout.

Directional icons and RTL in Vue

Mirror arrows, never logos, in RTL layouts. For Vue SVG icons, directional glyphs such as back and forward must flip when the document direction changes, and Vue SVG icons that ignore direction confuse users who read right to left. Teams implementing how to use SVG icons in Vue 3 for international products should rely on logical CSS properties that flip automatically, because Vue SVG icons handled with manual overrides rot with every redesign. The standard for how to use SVG icons in Vue 3 internationally is a documented list of directional glyphs, so Vue SVG icons mirror exactly where they should and nowhere else.

Verify RTL with a real locale run. For Vue SVG icons, screenshots in English cannot prove RTL correctness, and Vue SVG icons in a mirrored layout must be checked by running the app in an RTL locale and navigating through it. Teams testing how to use SVG icons in Vue 3 across locales should add one RTL screen to their visual regression suite, because Vue SVG icons that fail there are cheap to fix and costly to ship. The discipline for how to use SVG icons in Vue 3 at global scale is a locale pass before every release, so Vue SVG icons never become an afterthought for half the world's readers.

A request process for new Vue icons

Route new glyphs through a request template. For Vue SVG icons, a short template asking for the meaning, the screen and the nearest existing glyph prevents most duplicates, and Vue SVG icons added through requests stay inside the documented family. Teams formalising how to use SVG icons in Vue 3 at scale should review requests in the same meeting as design-system changes, because Vue SVG icons requested during feature work are how second families enter a codebase. The habit for how to use SVG icons in Vue 3 in growing teams is weekly request review, so Vue SVG icons grow deliberately rather than accidentally.

Answer requests with the existing set first. For Vue SVG icons, the most common outcome of a request is discovering the set already covers it, and Vue SVG icons found this way cost nothing and keep the system intact. Teams stewarding how to use SVG icons in Vue 3 should reply to each request with a screenshot of the closest existing glyph at production size, because Vue SVG icons judged at poster size mislead requesters. The principle for how to use SVG icons in Vue 3 over years is slow, deliberate growth, so Vue SVG icons added today still belong to the same family tomorrow.

Onboarding developers onto the Vue icon system

Teach the wrapper on day one. For Vue SVG icons, a new developer's first lesson should be the AppIcon wrapper, the size tokens and the allow-list map, and Vue SVG icons explained through the wrapper become a system rather than a pile of imports. Teams onboarding around how to use SVG icons in Vue 3 should put icon conventions in the onboarding checklist, because Vue SVG icons are often the first code a newcomer touches. The guidance for how to use SVG icons in Vue 3 in hiring teams is that conventions taught early are conventions kept, so Vue SVG icons stay consistent through turnover.

Review the first icon change carefully. For Vue SVG icons, a newcomer's first glyph addition or swap, reviewed against the checklist, teaches more than any document, and Vue SVG icons handled in a reviewed first task set the standard for everything after. Teams practising how to use SVG icons in Vue 3 with new hires should treat that review as teaching, because Vue SVG icons reviewed early prevent habits that audits later punish. The rule for how to use SVG icons in Vue 3 at team scale is that every developer's first icon change is reviewed, so Vue SVG icons remain a shared standard.

Frequently asked questions

Which Vue icon approach is best for Nuxt?

For a single Lucide-based system, lucide-vue-next renders perfectly in Nuxt SSR with no extra machinery. If you need several sets with server rendering, @nuxt/icon with local @iconify-json collections is the purpose-built option. Avoid API-mode Iconify in Nuxt, because icons that arrive after hydration cause layout shift and visible flashing.

Does @iconify/vue work offline?

Only if you bundle the data. By default the component fetches missing icons from the Iconify API at runtime. Installing the matching @iconify-json package and registering it with addCollection, or registering individual icons with addIcon, makes rendering fully local and offline-safe, and it also fixes SSR output because the server can resolve names immediately.

How do I change icon colour in Vue templates?

Set the CSS color on the icon or an ancestor and make sure the SVG uses currentColor, which every approach in this guide does by default. Tailwind users can use text colour utilities directly on the component. If an icon refuses to change colour, inspect it for hard-coded fill attributes and remove them.

Are unplugin-icons components tree-shaken?

Yes. unplugin-icons resolves each ~icons import to a concrete component at build time, so unused icons never enter the bundle and there is no runtime registry. The costs are build configuration and generated type declarations, not bytes. It is the closest Vue gets to the ergonomics of an icon font with the economics of inline SVG.

Can I mix lucide-vue-next and local custom icons?

Yes, and most production apps do. Keep custom glyphs in components/icons with the same prop contract (size plus inherited class), and they will sit invisibly next to package icons. The rule is about visual systems, not sources: one grid, one stroke logic, one sizing scale, regardless of which file a glyph lives in.

vuenuxtlucide-vue-nexticonify

Find the icons from this guide

Search 421,020 free SVG icons from 239 libraries in one place.

Browse icons