This documentation covers the complete FontAwesome integration with Vue 3. Related references:
Vue 3 projects need vue-fontawesome v3. Core SVG engine is same as React but the Vue component package is independently maintained. FontAwesome's Vue component renders icons as inline SVG elements, deeply integrated with Vue's reactivity system and virtual DOM for efficient icon updates and animation control.
npm install @fortawesome/vue-fontawesome@latest @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons# Regular style (outline icons)
npm install @fortawesome/free-regular-svg-icons
# Brand Icons
npm install @fortawesome/free-brands-svg-iconsFontAwesome Vue components support global and local registration. After global registration, use <font-awesome-icon> directly in any template, suitable for medium-to-large projects with frequent icon usage. Local registration only takes effect within specific component scope, suitable for projects with few icons or strict bundle size requirements. In practice, choose registration strategy flexibly based on project scale, or combine both approaches.
Register globally in main.js/main.ts, suitable for medium to large projects.
import { createApp } from 'vue'
import App from './App.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faHome, faUser, faGear } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
library.add(faHome, faUser, faGear)
createApp(App)
.component('font-awesome-icon', FontAwesomeIcon)
.mount('#app')Register locally in single components, suitable for small projects or specific pages.
<script setup>
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faStar } from '@fortawesome/free-solid-svg-icons/faStar'
</script>
<template>
<div>
<font-awesome-icon :icon="faStar" />
</div>
</template>The <font-awesome-icon> component core props are similar to React, supporting icon, size, spin, pulse, beat and other common props. Unlike directly using CSS class names, the component approach integrates better with Vue's data binding and conditional rendering, enabling flexible dynamic icon display.
<!-- Basic icon -->
<font-awesome-icon icon="fa-solid fa-home" />
<!-- Set size -->
<font-awesome-icon icon="fa-solid fa-star" size="2x" />
<!-- Spin animation -->
<font-awesome-icon icon="fa-solid fa-spinner" spin />
<!-- Pulse animation -->
<font-awesome-icon icon="fa-solid fa-circle-notch" pulse />
<!-- Custom style -->
<font-awesome-icon
icon="fa-solid fa-heart"
beat
style="color: red;"
class="my-icon"
/>The string format "fa-solid fa-home" requires the icon to be registered via library.add(). You can also pass the imported icon object directly.
Using Composition API in <script setup> gives more flexible icon import management with tree-shaking optimization. Composition API is Vue 3's recommended development pattern, organizing component logic into composable functions that lower coupling between icon management and business logic. Through library.add() combined with single icon imports, each component's icon dependencies are clearly visible for easier maintenance and refactoring.
<script setup>
import { ref } from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faSun, faMoon, faCheck, faXmark } from '@fortawesome/free-solid-svg-icons'
// Add to library for string reference
library.add(faSun, faMoon, faCheck, faXmark)
const isDark = ref(false)
function toggleTheme() {
isDark.value = !isDark.value
}
</script>
<template>
<button @click="toggleTheme" class="btn btn-outline-secondary">
<font-awesome-icon
:icon="isDark ? 'fa-solid fa-moon' : 'fa-solid fa-sun'"
class="me-1"
/>
{{ isDark ? 'Dark Mode' : 'Light Mode' }}
</button>
</template>Add to library for string reference
For optimal bundle size, import from single icon paths to ensure webpack/vite tree-shaking works. Tree-shaking is a core optimization in modern build tools, identifying and removing unreferenced code through static analysis of import statements. FontAwesome's icon library contains tens of thousands of icons; importing all would add megabytes to your bundle. Through single icon path imports, build tools can precisely retain only the icons actually used, which is especially important for large enterprise projects.
// Best practice: import single icon, supports tree-shaking
import { faHome } from '@fortawesome/free-solid-svg-icons/faHome'
import { faUser } from '@fortawesome/free-solid-svg-icons/faUser'
import { faBell } from '@fortawesome/free-solid-svg-icons/faBell'
import { library } from '@fortawesome/fontawesome-svg-core'
library.add(faHome, faUser, faBell)
// Avoid full import (significantly increases bundle size)
// import { faHome, faUser, faBell } from '@fortawesome/free-solid-svg-icons'
// Avoid FontAwesome JS auto-replace (dom.watch), it has compatibility issues with Vue's virtual DOM
// It has compatibility issues with Vue’s virtual DOM mechanismSingle icon path format: @fortawesome/free-solid-svg-icons/faXxx, PascalCase file naming. Both Vite and Webpack 5 correctly identify and tree-shake these imports.
| Comparison Item | Vue 3 | Vue 2 |
|---|---|---|
| Package Version | @fortawesome/vue-fontawesome@3 | @fortawesome/vue-fontawesome@2 |
| Component Tag | <font-awesome-icon> | <font-awesome-icon>(Same) |
| Composition API | Vue 3 <script setup> | Vue 2 @vue/composition-api |
| Icon Binding | :icon="faHome" or icon="fa-solid fa-home" | (Same) |
| Same as Vue 3 | Full TypeScript types | No native TS support |
| SSR Type Support | Native support (Nuxt 3) | Extra config needed (Nuxt 2) |
| Animation Props | spin, pulse, beat, fadespin, pulse, beat, fade all supported | Basic spin animation support |
Migration advice: New projects always use Vue 3 + vue-fontawesome@3; existing Vue 2 projects stay on v2, do not mix versions.
Embed icons in buttons with loading state for better user feedback.
<script setup>
import { ref } from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faSpinner, faDownload, faCheck } from '@fortawesome/free-solid-svg-icons'
library.add(faSpinner, faDownload, faCheck)
const loading = ref(false)
async function handleDownload() {
loading.value = true
await new Promise(r => setTimeout(r, 2000))
loading.value = false
}
</script>
<template>
<button
@click="handleDownload"
:disabled="loading"
class="btn btn-primary"
>
<font-awesome-icon
:icon="loading ? 'fa-solid fa-spinner' : 'fa-solid fa-download'"
:spin="loading"
class="me-1"
/>
{{ loading ? 'Downloading...' : 'Start Download' }}
</button>
</template>Add icons before list items to improve readability and visual hierarchy.
<script setup>
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faCircleCheck, faCircleXmark, faCircleExclamation } from '@fortawesome/free-solid-svg-icons'
library.add(faCircleCheck, faCircleXmark, faCircleExclamation)
const items = [
{ text: 'Database connected', status: 'success' },
{ text: 'Config validation passed', status: 'success' },
{ text: 'Cache warmed', status: 'warning' },
{ text: 'Email service not configured', status: 'error' },
]
const statusMap = {
success: { icon: 'fa-solid fa-circle-check', color: '#20c997' },
warning: { icon: 'fa-solid fa-circle-exclamation', color: '#f59f00' },
error: { icon: 'fa-solid fa-circle-xmark', color: '#e03131' },
}
</script>
<template>
<ul class="list-unstyled">
<li v-for="item in items" :key="item.text" class="mb-2">
<font-awesome-icon
:icon="statusMap[item.status].icon"
:style="{ color: statusMap[item.status].color }"
fixed-width
class="me-2"
/>
{{ item.text }}
</li>
</ul>
</template>Switching icons based on component state is a simple and effective feedback pattern.
<script setup>
import { ref } from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'
library.add(faEye, faEyeSlash)
const visible = ref(false)
const password = ref('')
</script>
<template>
<div class="input-group">
<input
:type="visible ? 'text' : 'password'"
v-model="password"
class="form-control"
placeholder="Enter password"
/>
<button class="btn btn-outline-secondary" @click="visible = !visible">
<font-awesome-icon :icon="visible ? 'fa-solid fa-eye' : 'fa-solid fa-eye-slash'" />
</button>
</div>
</template>In Nuxt 3, create a plugin file to register FontAwesome components globally, avoiding duplicate imports. Nuxt 3's plugin system auto-loads during app initialization, making registered components available app-wide. For SSR projects, this ensures consistent server and client rendering, avoiding layout shifts from client hydration.
// plugins/fontawesome.js
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faHome, faUser, faGear } from '@fortawesome/free-solid-svg-icons'
import { faGithub, faTwitter } from '@fortawesome/free-brands-svg-icons'
library.add(faHome, faUser, faGear, faGithub, faTwitter)
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.component('font-awesome-icon', FontAwesomeIcon)
})<!-- Use directly in any page after registration -->
<template>
<div>
<font-awesome-icon icon="fa-solid fa-home" size="2x" />
<font-awesome-icon :icon="['fab', 'github']" size="2x" />
</div>
</template>Use directly in any page after registration