Aller au contenu principal

Symfony UX Icons : 200 000 icônes SVG sans CDN, sans sprite, sans prise de tête

Symfony UX Icons intègre 200 000+ icônes SVG via Iconify, sans CDN ni sprite. Installation et usage en une ligne Twig pour une UX sans prise de tête.

MAJ 6 min de lecture
Sommaire · 6

L'argument de vente d'UX Icons est l'abondance : 200 000 icônes, 150 collections. Sur ce blog, 71 des 78 icônes viennent d'une seule collection. Ce que le bundle a réellement apporté est ailleurs, et ça tient dans une ligne de configuration qui n'a pas la même valeur en dev et en prod.

Ce que je pensais acheter, et ce que j'ai utilisé

L'argument d'UX Icons tient en un chiffre : plus de 200 000 icônes, agrégées par Iconify depuis 150 collections, accessibles sans rien télécharger.

Le compte réel sur ce blog : 78 icônes appelées, dont 71 en tabler:, une en lucide:, et une poignée dont le nom est calculé à l'exécution. Une collection sur 150.

Ce n'est pas une déception, c'est un enseignement sur ce qu'on achète vraiment quand on installe ce genre de bundle. L'abondance sert une seule fois, au moment de choisir sa collection. Ce qui sert tous les jours, c'est ce que le bundle empêche d'écrire, et ce qu'il fait quand une icône manque.

Ce qu'il y avait avant, et pourquoi ça n'allait pas

Trois façons de poser une icône dans une page, et chacune se paie.

La police d'icônes servie par un CDN charge une feuille de style et un fichier de police entiers pour afficher une dizaine de glyphes. Le coût est un blocage de rendu, plus une requête vers un tiers, ce qui pose aujourd'hui autant une question de vie privée que de performance.

Le sprite SVG est performant et pénible : ajouter une icône veut dire régénérer le fichier, et le fichier grossit sans que personne ne retire jamais rien.

Les fichiers SVG posés à la main dans public/ finissent en dossier de deux cents entrées sans convention de nommage. Et l'appel par <img src="…"> interdit d'agir sur l'intérieur du SVG, donc la couleur au survol et l'épaisseur du trait deviennent impossibles.

Le point commun des trois : l'icône est un actif qu'on gère, alors qu'on voudrait que ce soit un mot qu'on écrit.

Le mécanisme, en trois temps

UX Icons ne charge rien au moment où le visiteur affiche la page. Il récupère le SVG depuis l'API Iconify au premier usage en développement, ou à la compilation en production, le range dans le cache Symfony, et l'injecte directement dans le HTML.

Twig
{{ ux_icon('tabler:search', { class: 'w-5 h-5' }) }}

Ce qui sort est un <svg> complet dans la page. Pas de requête réseau, pas de fichier à servir, pas de police à charger. Et comme le SVG est inline, le CSS de la page agit dessus : la couleur suit currentColor, la taille suit la typographie.

La configuration de ce projet tient en trois attributs par défaut :

PHP
// config/packages/ux_icons.php
$uxIcons = [
    'default_icon_attributes' => [
        'fill' => 'currentColor',
        'height' => '1em',
        'width' => '1em',
    ],
    'ignore_not_found' => false,
];

if ($env === 'prod') {
    $uxIcons['ignore_not_found'] = true;
}

currentColor et les dimensions en em font qu'une icône se comporte comme un caractère : elle hérite de la couleur et de la taille du texte qui l'entoure. Aucune classe à poser pour qu'elle s'accorde au thème sombre.

La ligne qui compte vraiment

Les quatre dernières lignes de cette configuration sont l'apport réel du bundle, et l'annonce n'en parle jamais.

ignore_not_found à false en développement : un nom d'icône mal orthographié fait échouer le rendu, tout de suite, avec le nom fautif dans le message. On corrige avant de commiter.

Le même réglage à true en production : la même faute ne rend rien du tout. Pas de page d'erreur, pas de trou visible, juste une icône absente dans une interface qui continue de fonctionner.

Une icône manquante doit hurler chez le développeur et se taire chez le lecteur. C'est une règle qui vaut bien au-delà des icônes, et elle est rarement posée aussi proprement : la plupart des bibliothèques choisissent un comportement unique et vous laissent l'assumer dans les deux environnements.

Le corollaire mérite d'être dit, parce qu'il coûte : en production, une icône disparue ne prévient personne. Le filet est du côté du développement, et il n'y en a pas d'autre.

Ce que ça a supprimé, mesuré

Le meilleur indicateur d'adoption n'est pas le nombre d'icônes posées, c'est ce qui a cessé d'être écrit.

Sur l'ensemble des gabarits de ce blog : zéro balise <svg> écrite à la main. Pas une. C'est même devenu une règle de projet, au même titre que l'interdiction d'appeler un repository depuis un contrôleur.

Une convention tient rarement par discipline. Elle tient quand l'alternative est plus simple que la transgression. Écrire {{ ux_icon('tabler:search') }} demande moins d'efforts que d'aller chercher un SVG, le nettoyer et le coller : c'est pour ça que la règle n'a jamais eu besoin d'être rappelée.

Ce que je referais pareil, et ce que je surveille

Une seule collection, choisie une fois. Mélanger les jeux d'icônes est le risque que l'abondance fait courir : deux collections ont deux grilles, deux épaisseurs de trait, deux façons de dessiner un chevron, et l'incohérence se voit sur une barre d'outils bien avant de se voir sur une page.

Le point que je surveille est ailleurs. Le SVG est récupéré depuis l'API Iconify et mis en cache, ce qui veut dire qu'un cache vidé au mauvais moment fait dépendre une compilation d'un service tiers. Sur ce blog les icônes sont figées à la compilation, donc l'incident serait un déploiement en échec, pas une page cassée. C'est une nuance rassurante, et c'est quand même une dépendance réseau dans une chaîne de build.

Reste la question que le chiffre de 200 000 empêche de se poser. Combien de collections votre interface utilise-t-elle réellement, et est-ce que c'était une décision ?

Ce billet est publié sous licence Creative Commons BY-NC-SA 4.0 (attribution, pas d'usage commercial, partage dans les mêmes conditions).

Vous aimerez aussi

Activez uniquement ce que vous souhaitez. Vos choix sont conservés 6 mois.

Strictement nécessaires

Indispensables au fonctionnement du site (session, sécurité, préférence d'affichage). Aucune donnée n'est partagée à des tiers et aucun consentement n'est requis.

Toujours actif

Mesure d'audience

Statistiques via Google Analytics (GA4) : pages vues, source du trafic, navigateur et interactions clés. Dépose des cookies de mesure, activés seulement avec votre accord (Consent Mode). Sans publicité ciblée, sans Google Signals, sans partage commercial.

Contenus externes

Affiche les GIF animés hébergés par Giphy (CDN aux États-Unis). À l'affichage d'un GIF, votre adresse IP et votre navigateur sont transmis à Giphy. Sans votre accord, les GIF ne s'affichent pas.