Utiliser des icônes dans un projet avec VueDsfr
Utiliser les icônes officielles
Pour utiliser les icônes officielles avec les classes CSS du DSFR, il n’y a pas d’actions en plus à faire, puisque c’est le CSS officiel du DSFR qui est utilisé.
Ci-dessous un exemple :
<template>
<div class="fr-p-2w">
<p>
Une icône dans un <span> ici : <span class="fr-icon-checkbox-circle-line" />
</p>
<button class="fr-btn fr-icon-checkbox-circle-line fr-btn--icon-left">
Une icône dans un bouton ici
</button>
</div>
</template>
Utiliser les icônes dans les composants de VueDsfr
Plusieurs composants (DsfrButton
, DsfrBadge
, DsfrCallout
...) ont la prop icon
qui permet d’ajouter une icône.
Attention, cette icône n’est pas forcément une icône officielle du DSFR. En effet, VueDsfr utilise désormais (depuis la v6) la bibliothèque @iconify/vue
. Cette prop icon
est donc :
- soit une
string
qui doit être un nom de classe valide pour une icône du DSFR (qui commence par'fr-icon-'
) ; - soit une
string
qui doit être un nom d’icône valide pour iconify; - soit la prop complète attendue par le composant
VIcon
de la bibliothèque dont voici le type :tsexport type VIconProps = { name?: string scale?: string | number verticalAlign?: string animation?: 'spin' | 'wrench' | 'pulse' | 'spin-pulse' | 'flash' | 'float' | 'ring' speed?: 'fast' | 'slow' flip?: 'horizontal' | 'vertical' | 'both' label?: string title?: string color?: string fill?: string inverse?: boolean ssr?: boolean display?: 'block' | 'inline-block' | 'inline' }
Démo
<script lang="ts" setup>
import DsfrButton from '../../src/components/DsfrButton/DsfrButton.vue'
</script>
<template>
<div class="fr-p-2w flex gap-4 flex-wrap">
<DsfrButton icon="fr-icon-close-line">
Texte avec icône Dsfr (commence par <code>'fr-'</code>)
</DsfrButton>
<DsfrButton icon="ri-close-line">
Texte avec icône @iconify/vue
</DsfrButton>
<DsfrButton
:icon="{ name: 'ri-close-line', color: 'green', animation: 'flash' }"
secondary
>
Texte avec icône
<code>@iconify/vue</code>
<span style="color: green"> verte </span>
avec animation 'flash'
</DsfrButton>
</div>
</template>
<style scoped>
.flex {
display: flex;
}
.gap-4 {
gap: 1rem;
}
</style>
WARNING
Il faut normalement utiliser le nom en kebab-case et avec le nom de la collection en prefix séparé par un caractère :
comme par exemple ri:close-line
exemple :
<template>
Nom d’icône correct : <VIcon name="ri:close-line" />
</template>
Cependant, si le préfixe est lui-même sans tiret -
, alors l’écriture tout en kebab-case est acceptée :
<template>
Nom d’icône accepté : <VIcon name="ri-close-line" />
</template>
Les collections disponibles
Vous pouvez utiliser toutes les icônes disponibles ici : icon-sets.iconify.design
// src/components/MonComposant.vue
<script lang="ts" setup>
import { ref } from 'vue'
import { VIcon } from '@gouvminint/vue-dsfr'
</script>
<template>
<div>
<VIcon
name="ri-checkbox-circle-line"
scale="2"
animation="float"
label="icon label"
title="icon title"
/>
C’est validé !
</div>
</template>
Éviter les appels réseaux (optionnel - pour les applications internes)
Si vous développez des applications destinées à des agents internes avec potentiellement des accès internet réduits, il est possible que les appels vers l’API iconify soient bloqués. Vous voudrez donc éviter ces appels réseaux.
Dans ce but, depuis la version 8.0.0, la dépendance @iconify/vue
n’est plus incluse dans la bibliothèque, et doit être installée dans votre application.
Avec cette modification, il est possible d’ajouter des collections d’icônes qui ne feront pas d’appels réseaux.
TL;DR;
créer un fichier
scripts/icons.js
dans votre projet avec un contenu de cette forme :js// @ts-check import { icons as mdiCollection } from '@iconify-json/mdi' import { icons as riCollection } from '@iconify-json/ri' /** * Liste de nom d’icônes **sans** le préfixe de la collection Remix Icons qui sont utilisées dans l’application * @type {string[]} */ const riIconNames = [ 'flag-line', 'home-4-line', 'question-mark', ] /** * Liste de nom d’icônes **sans** le préfixe de la collection Material Design Icons qui sont utilisées dans l’application * @type {string[]} */ const mdiIconNames = [ 'account-heart', 'account-key', ] /** * Liste de tuples [collectionDIcônes, tableauDeNomsDIcônesUtiliséesDansLApplication] * @type {[import('@iconify/vue').IconifyJSON, string[]][]} */ export const collectionsToFilter = [ [riCollection, riIconNames], [mdiCollection, mdiIconNames], ]
N.B. : l’exemple ci-dessus montre comment utiliser les icônes
'ri-flag-line'
,'ri-home-4-line'
,'ri-question-mark'
de la collection remix icons (ri
) et les icônes'mdi-account-heart'
et'mdi-account-key'
de la collection Material Design Icons (mdi
).ajouter un script
"icons"
dans lepackage.json
de votre application avec cette commande:"vue-dsfr-icons -s scripts/icons.js -t src/icon-collections.ts"
lancer le script
icons
(e.g. :npm run icons
si vous utiliseznpm
comme gestionnaire de paquet) à chaque fois que vous modifiez le fichierscripts/icons.js
. Pour le fichierscripts/icons.js
, cela générera le fichiersrc/icon-collections.ts
suivant :tsimport type { IconifyJSON } from '@iconify/vue' const collections: IconifyJSON[] = [{ prefix: 'ri', icons: { 'flag-line': { body: '<path fill="currentColor" d="M12.382 3a1 1 0 0 1 .894.553L14 5h6a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1h-6.382a1 1 0 0 1-.894-.553L12 16H5v6H3V3zm-.618 2H5v9h8.236l1 2H19V7h-6.236z"/>' }, 'home-4-line': { body: '<path fill="currentColor" d="M19 21H5a1 1 0 0 1-1-1v-9H1l10.327-9.388a1 1 0 0 1 1.346 0L23 11h-3v9a1 1 0 0 1-1 1m-6-2h5V9.157l-6-5.454l-6 5.454V19h5v-6h2z"/>' }, 'question-mark': { body: '<path fill="currentColor" d="M12 19a1.5 1.5 0 1 1 0 3a1.5 1.5 0 0 1 0-3m0-17a6 6 0 0 1 6 6c0 2.165-.753 3.29-2.674 4.923C13.399 14.56 13 15.297 13 17h-2c0-2.474.787-3.695 3.031-5.601C15.548 10.11 16 9.434 16 8a4 4 0 0 0-8 0v1H6V8a6 6 0 0 1 6-6"/>' } }, width: 24, height: 24 }, { prefix: 'mdi', icons: { 'account-heart': { body: '<path fill="currentColor" d="M15 14c-2.7 0-8 1.3-8 4v2h16v-2c0-2.7-5.3-4-8-4m0-2a4 4 0 0 0 4-4a4 4 0 0 0-4-4a4 4 0 0 0-4 4a4 4 0 0 0 4 4M5 15l-.6-.5C2.4 12.6 1 11.4 1 9.9c0-1.2 1-2.2 2.2-2.2c.7 0 1.4.3 1.8.8c.4-.5 1.1-.8 1.8-.8C8 7.7 9 8.6 9 9.9c0 1.5-1.4 2.7-3.4 4.6z"/>' }, 'account-key': { body: '<path fill="currentColor" d="M11 10v2H9v2H7v-2H5.8c-.4 1.2-1.5 2-2.8 2c-1.7 0-3-1.3-3-3s1.3-3 3-3c1.3 0 2.4.8 2.8 2zm-8 0c-.6 0-1 .4-1 1s.4 1 1 1s1-.4 1-1s-.4-1-1-1m13 4c2.7 0 8 1.3 8 4v2H8v-2c0-2.7 5.3-4 8-4m0-2c-2.2 0-4-1.8-4-4s1.8-4 4-4s4 1.8 4 4s-1.8 4-4 4"/>' } }, width: 24, height: 24 }] export default collections
ou formatté autrement :
tsimport type { IconifyJSON } from '@iconify/vue' const collections: IconifyJSON[] = [{ prefix: 'ri', icons: { 'flag-line': { body: '<path fill="currentColor" d="M12.382 3a1 1 0 0 1 .894.553L14 5h6a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1h-6.382a1 1 0 0 1-.894-.553L12 16H5v6H3V3zm-.618 2H5v9h8.236l1 2H19V7h-6.236z"/>' }, 'home-4-line': { body: '<path fill="currentColor" d="M19 21H5a1 1 0 0 1-1-1v-9H1l10.327-9.388a1 1 0 0 1 1.346 0L23 11h-3v9a1 1 0 0 1-1 1m-6-2h5V9.157l-6-5.454l-6 5.454V19h5v-6h2z"/>' }, 'question-mark': { body: '<path fill="currentColor" d="M12 19a1.5 1.5 0 1 1 0 3a1.5 1.5 0 0 1 0-3m0-17a6 6 0 0 1 6 6c0 2.165-.753 3.29-2.674 4.923C13.399 14.56 13 15.297 13 17h-2c0-2.474.787-3.695 3.031-5.601C15.548 10.11 16 9.434 16 8a4 4 0 0 0-8 0v1H6V8a6 6 0 0 1 6-6"/>' }, }, width: 24, height: 24, }, { prefix: 'mdi', icons: { 'account-heart': { body: '<path fill="currentColor" d="M15 14c-2.7 0-8 1.3-8 4v2h16v-2c0-2.7-5.3-4-8-4m0-2a4 4 0 0 0 4-4a4 4 0 0 0-4-4a4 4 0 0 0-4 4a4 4 0 0 0 4 4M5 15l-.6-.5C2.4 12.6 1 11.4 1 9.9c0-1.2 1-2.2 2.2-2.2c.7 0 1.4.3 1.8.8c.4-.5 1.1-.8 1.8-.8C8 7.7 9 8.6 9 9.9c0 1.5-1.4 2.7-3.4 4.6z"/>' }, 'account-key': { body: '<path fill="currentColor" d="M11 10v2H9v2H7v-2H5.8c-.4 1.2-1.5 2-2.8 2c-1.7 0-3-1.3-3-3s1.3-3 3-3c1.3 0 2.4.8 2.8 2zm-8 0c-.6 0-1 .4-1 1s.4 1 1 1s1-.4 1-1s-.4-1-1-1m13 4c2.7 0 8 1.3 8 4v2H8v-2c0-2.7 5.3-4 8-4m0-2c-2.2 0-4-1.8-4-4s1.8-4 4-4s4 1.8 4 4s-1.8 4-4 4"/>' }, }, width: 24, height: 24, }] export default collections
ajouter les collections dans votre point d’entrée (généralement
src/main.ts
) :ts// (...) import { collections } from './icon-collections.js' // (...) for (const collection of collections) { addCollection(collection) } // (...)
Plus d’explication pour éviter les appels réseaux
En interne, depuis la version 6.0.0
VueDsfr utilise iconify. Pour comprendre la section précédente, il faut comprendre comment fonctionne iconify.
Iconify
INFO
Veuillez consulter la documentation officielle de @iconify/vue pour plus de détails.
Le principe de iconify est de ne pas inclure les icônes dans le bundle et de faire un appel réseau en arrière-plan (XHR ou fetch) pour récupérer les SVG des icônes utilisées à la demande, c’est-à-dire dès qu’un composant qui utilise des icônes iconify est rendu dans le DOM.
Or ces appels API nécessitent pour l’utilisateur de l’application d’avoir accès à internet, car par défaut l’API utilisée pour récupérer les icônes est celle d’iconify, sur internet.
Il est possible d’héberger soi-même un serveur API et de dire à iconify d’utiliser cette API. C’est néanmoins compliqué.
Il est possible aussi d’utiliser la fonction addCollection(collection: IconifyJSON)
exposée par @iconify/vue
pour inclure des icônes et faire en sorte qu’aucun appel réseau ne soit effectué.
Le plus simple, c’est donc d’utiliser un sous-ensemble d’une collection existante (par exemple @iconify-json/ri
pour Remix Icons) en créant une nouvelle collection et d’ajouter cette collection. C’est ce que fait la fonction filterIcons (collection: import('@iconify/vue').IconifyJSON, iconNames: string[])
exposée par @gouvminint/vue-dsfr/meta
. Vous ne voudrez sans doute pas l’utiliser directement.
Cette fonction filterIcons()
est utilisée par la fonction createCustomCollectionFile (sourcePath: string, targetPath: string)
aussi exposée par @gouvminint/vue-dsfr/meta
, dont voici la partie importante :
// (...)
const collectionsToFilter = await import(sourcePath).then(({ collectionsToFilter }) => collectionsToFilter)
const collections = collectionsToFilter.map(tuple => filterIcons(...tuple))
const code = `import type { IconifyJSON } from '@iconify/vue'
const collections: IconifyJSON[] = ${JSON.stringify(collections)}
export default collections`
// (...)
TIP
sourcePath
est le chemin vers le fichier qui contient le code listé plus dans le fichierscripts/icons.js
targetPath
est le chemin vers le fichier qui contiendra le code généré et appelé plus hautsrc/icon-collections.js
Libre à vous d’adapter les chemins et les noms de fichiers, veillez simplement à modifier en fonction le script "icons"
de package.json
et l’import dans votre fichier d’entrée (souvent src/main.ts
).
TIP
Nous vous invitons à regarder les fichiers suivants :
meta/custom-icon-collections-creator.js
meta/custom-icon-collections-creator-bin.js
(celui qui est aliasé en binairevue-dsfr-icons
et qui utilisecustom-icon-collections-creator.js
)
Pour Nuxt 3
Veillez simplement à utiliser la prop ssr
à true
.
Plus de détails dans la documentation officielle de @iconify/vue pour le SSR.