Frameworks

Icônes dans Lit et les web components

glyphfox fournit un élément personnalisé, <glyph-fox>, qui reçoit le nom de l'icône et toutes les props communes sous forme d'attributs. Il s'utilise partout où l'on peut écrire du HTML et s'intègre aux templates Lit sans wrapper.

Installation

Installez le paquet et enregistrez l'élément une seule fois, dans le module d'entrée de votre application. Importer glyphfox/web enregistre toutes les icônes et définit <glyph-fox> par effet de bord : vous n'avez rien d'autre à faire.

terminal
npm install glyphfox
main.js
import 'glyphfox/web';

Pour n'embarquer que les icônes que vous utilisez, importez l'élément depuis glyphfox/web/element, enregistrez les données des icônes depuis glyphfox/icons/NAME et définissez l'élément vous-même. registerIcons provient de glyphfox/core, qui partage son registre avec l'élément.

icons.js
import { defineGlyphFox } from 'glyphfox/web/element';
import { registerIcons } from 'glyphfox/core';
import menu from 'glyphfox/icons/menu';
import x from 'glyphfox/icons/x';
import bell from 'glyphfox/icons/bell';
import search from 'glyphfox/icons/search';

registerIcons([menu, x, bell, search]);
defineGlyphFox();
Point d'entréeContenuTaille (esbuild, minifié)
glyphfox/webL'élément, le moteur et toutes les icônes, enregistrés à l'import.environ 209 Ko, 59 Ko en gzip
glyphfox/web/elementL'élément et le moteur ; c'est vous qui enregistrez les icônes.environ 77 Ko, 28 Ko en gzip avec une icône, plus quelques centaines d'octets par icône

Un nom que vous n'avez pas enregistré est chargé à la demande. webpack et esbuild transforment ces chargements en un petit chunk par icône, mais un build de production Vite laisse l'import tel quel dans node_modules et l'icône reste vide (voir le guide sur le tree shaking). Enregistrez toutes les icônes que vous utilisez avec glyphfox/web/element : elles s'affichent alors aussi dès le premier rendu, sans aller-retour réseau.

Utiliser l'élément

L'élément rend un SVG inline dans son shadow root. name accepte la syntaxe complète des noms : les badges de modificateur, les fusions et les styles ne demandent donc aucune API supplémentaire. Chaque prop commune est un attribut en kebab-case.

index.html
<glyph-fox name="home"></glyph-fox>
<glyph-fox name="file+check" size="32" weight="1.5"></glyph-fox>
<glyph-fox name="cloud&lock" layout="overlap"></glyph-fox>
<glyph-fox name="heart/solid" color="#e11d48"></glyph-fox>
<glyph-fox name="bell" animate="shake" trigger="hover"></glyph-fox>

L'élément observe ses attributs. Modifiez-en un avec setAttribute, ou via n'importe quel template qui écrit des attributs, et l'icône se redessine avec la nouvelle valeur.

Templates Lit

Dans Lit, passez les valeurs par des liaisons d'attribut. L'élément lit des attributs et n'expose aucune propriété JavaScript correspondante : une liaison de propriété comme .name=${...} définit donc un champ que l'élément ne lit jamais, et l'icône reste inchangée.

app-toolbar.js
import { LitElement, html, css } from 'lit';
import 'glyphfox/web';

export class AppToolbar extends LitElement {
  static properties = {
    open: { type: Boolean },
    unread: { type: Number },
  };

  static styles = css`
    button { display: inline-flex; align-items: center; gap: 8px; color: inherit; }
    glyph-fox { color: var(--toolbar-icon, currentColor); }
  `;

  constructor() {
    super();
    this.open = false;
    this.unread = 0;
  }

  render() {
    return html`
      <button type="button" aria-expanded=${this.open} @click=${() => (this.open = !this.open)}>
        <glyph-fox name=${this.open ? 'x' : 'menu'} morph duration="300"></glyph-fox>
        <span>Menu</span>
      </button>
      <glyph-fox
        name="bell"
        animate="shake"
        trigger="state"
        ?active=${this.unread > 0}
        title=${this.unread ? `${this.unread} new notifications` : 'No new notifications'}
      ></glyph-fox>
    `;
  }
}

customElements.define('app-toolbar', AppToolbar);

Pour les flags, utilisez des liaisons booléennes : ?morph=${...} et ?active=${...}. L'élément interprète un attribut vide ou "true" comme vrai, et "false" ou un attribut absent comme faux : la correspondance avec une liaison booléenne est donc directe.

Pour les valeurs facultatives, liez nothing (ou utilisez la directive ifDefined) afin que l'attribut soit retiré en l'absence de valeur. Un attribut size vide donne au svg interne une largeur et une hauteur vides, et le navigateur applique alors sa taille par défaut pour les svg.

icon-button.js
import { html, nothing } from 'lit';

export const iconButton = ({ name, size, label, onClick }) => html`
  <button type="button" aria-label=${label} @click=${onClick}>
    <glyph-fox name=${name} size=${size ?? nothing}></glyph-fox>
  </button>
`;

Styliser à travers le shadow root

Le svg se trouve dans le shadow root de l'élément, avec ses traits en currentColor. La couleur est héritée à travers la frontière du shadow DOM : définissez donc color sur l'élément ou sur n'importe lequel de ses ancêtres. L'hôte est en display: inline-flex avec line-height: 0 et vertical-align: middle, ce qui lui permet de s'aligner sur une ligne de texte sans CSS supplémentaire.

La taille provient de l'attribut size, qui définit la largeur et la hauteur du svg interne. Une largeur et une hauteur CSS appliquées à l'hôte ne redimensionnent pas le dessin. Utilisez size="1em" pour suivre la taille de la police, ou renseignez l'attribut à partir de vos propres design tokens.

note.html
<p class="note">
  <glyph-fox name="circle-info" size="1em" weight="2.5"></glyph-fox>
  The icon follows the font size of this paragraph.
</p>
<glyph-fox name="folder" variant="duotone" color="#0f172a" secondary-color="#ff7a1a"></glyph-fox>
toolbar.css
.toolbar glyph-fox { color: #6b7280; }
.toolbar button:hover glyph-fox { color: #111827; }

Il n'y a ni parts ni slots à styliser : tout le contenu interne est généré à partir des props. Modifiez le dessin avec des attributs comme weight, corners, variant et secondary-color, et non avec des sélecteurs qui cherchent à pénétrer dans le shadow root.

Votre propre nom de balise

defineGlyphFox(tag) définit l'élément sous un autre nom et renvoie sa classe. Utilisez-la pour reprendre le préfixe de votre design system ou pour éviter un conflit avec un autre élément. Vous pouvez l'appeler plusieurs fois sans risque : une balise déjà définie est renvoyée telle quelle.

icons.js
import { defineGlyphFox } from 'glyphfox/web/element';
import { registerIcons } from 'glyphfox/core';
import home from 'glyphfox/icons/home';
import settings from 'glyphfox/icons/settings';

registerIcons([home, settings]);
defineGlyphFox('acme-icon');
index.html
<acme-icon name="home" size="20"></acme-icon>
<acme-icon name="settings" size="20" weight="1.5"></acme-icon>

Importer glyphfox/web définit aussi glyph-fox ; choisissez donc un seul point d'entrée par application : glyphfox/web pour la balise par défaut avec toutes les icônes, glyphfox/web/element lorsque c'est vous qui choisissez la balise et la liste des icônes.

Déclencheurs et morphs

L'élément gère lui-même ses interactions. Avec trigger="hover" ou trigger="click", il écoute les événements sur lui-même, et avec trigger="state", il suit l'attribut active. L'attribut morph anime chaque changement de name, tandis qu'avec morph-to, l'icône se transforme en une seconde icône à chaque déclenchement.

index.html
<glyph-fox name="menu" morph-to="x" trigger="click"></glyph-fox>
<glyph-fox name="sparkles" animate="draw" trigger="hover"></glyph-fox>
<glyph-fox id="visibility" name="eye" morph></glyph-fox>

<script type="module">
  const eye = document.getElementById('visibility');
  eye.addEventListener('click', () => {
    eye.setAttribute('name', eye.getAttribute('name') === 'eye' ? 'eye-off' : 'eye');
  });
</script>

Le survol et le clic sont détectés sur l'élément lui-même. Les styles d'animation se trouvent dans le shadow root, d'où ils ne peuvent pas voir le parent, et l'écouteur de clic est placé sur l'hôte. Lorsque tout un bouton doit réagir, pilotez l'icône depuis le bouton : utilisez trigger="state" et définissez active depuis les gestionnaires d'événements du bouton.

save-button.html
<button type="button" class="save">
  <glyph-fox name="save" animate="pop" trigger="state"></glyph-fox>
  Save
</button>

<script type="module">
  const button = document.querySelector('.save');
  const icon = button.querySelector('glyph-fox');
  button.addEventListener('pointerenter', () => icon.setAttribute('active', ''));
  button.addEventListener('pointerleave', () => icon.removeAttribute('active'));
</script>

Les morphs et les presets respectent prefers-reduced-motion : les transitions passent directement à leur état final et les animations ne sont pas jouées. Les presets et les déclencheurs sont décrits dans le guide des icônes animées.

SVG statique dans les templates Lit

Lorsqu'une icône ne change jamais après le rendu, vous pouvez vous passer de l'élément et insérer directement le balisage SVG. renderToString, exporté par glyphfox/core, rend les données d'une icône avec les props communes, et Lit insère la chaîne grâce à la directive unsafeHTML. Le moteur de rendu échappe les titres et les valeurs d'attributs : le balisage peut donc être inséré sans risque.

saved-badge.js
import { html } from 'lit';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { renderToString } from 'glyphfox/core';
import circleCheck from 'glyphfox/icons/circle-check';

const check = unsafeHTML(renderToString(circleCheck, { size: 16, color: '#16a34a' }));

export const savedBadge = () => html`<span class="badge">${check} Saved</span>`;

Pour la syntaxe des noms (file+check@tr, cloud&lock), utilisez toSvg, exporté par glyphfox. Il résout n'importe quel nom, ce qui implique qu'il embarque toutes les icônes ; lisez le guide sur le tree shaking avant de l'utiliser dans un bundle client.

Attributs

Chaque prop commune est un attribut en kebab-case. L'élément en ajoute quatre qui lui sont propres : name pour l'icône, avec la syntaxe complète des noms ; layout et gap pour les fusions ; et morph, qui anime les changements de name. Les nombres sont lus à partir du texte de l'attribut, et les booléens suivent la règle décrite plus haut.

AttributTypePar défautDescription
namestringaucunLa spécification de l'icône : un nom, name+modifier@corner, a&b:layout ou name/variant.
layoutoverlap, side, stack, grid, orbit, badge, insideaucunDisposition de la fusion quand le nom combine des icônes avec &.
gapnumber1Espace de découpe d'une fusion, en unités de la grille.
morphbooleanfalseAnime chaque changement de name sous forme de morph.
sizenumber, string24Largeur et hauteur ; les nombres sont en pixels.
colorstringcurrentColorCouleur principale.
secondary-colorstringcolorCouleur du calque duotone et du remplissage plein.
secondary-opacitynumber0.3 / 1Opacité du calque duotone ou du remplissage plein.
weightnumber2Trait de 1 à 3 sur la grille de 24, avec compensation optique en dessous de 24 px.
absolute-weightbooleanfalseGarde le trait à weight pixels quelle que soit la taille.
cornersround, sharproundJointures et extrémités arrondies ou vives.
variantoutline, duotone, solidoutlineAxe de style ; solid vient des formes fermées.
rotatenumber0Rotation en degrés.
flip-xbooleanfalseMiroir horizontal.
flip-ybooleanfalseMiroir vertical.
mirror-rtlbooleanfalseMiroir dans les mises en page de droite à gauche.
animatedraw, pop, wiggle, spin, pulse, bounce, shakeaucunPréréglage d'état vivant.
triggermount, hover, click, statemountMoment où l'animation ou le morph se joue.
activebooleanfalseL'état pour le déclencheur state.
durationnumber (ms)selon le préréglageDurée de l'animation ou du morph.
delaynumber (ms)0Délai de l'animation ou du morph.
loopboolean, numberfalseRépète à l'infini ou un certain nombre de fois (spin et pulse bouclent par défaut).
modifierplus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dotaucunBadge avec espace de découpe automatique.
modifier-cornertop-left, top-right, bottom-left, bottom-rightbottom-rightCoin du badge.
morph-toname, dataaucunCible du morph ; le déclencheur décide quand elle s'affiche.
titlestringaucunNom accessible : title, role img et aria-label.
decorativebooleantrue sans titleMasque l'icône aux technologies d'assistance.

Accessibilité

Sans libellé, l'icône est décorative : le svg interne reçoit aria-hidden="true". C'est le bon comportement pour une icône placée à côté d'un texte visible, et pour un bouton icône qui porte son propre aria-label.

Pour donner un nom à l'icône elle-même, utilisez l'attribut title. Il ajoute un <title>, role="img" et aria-label au svg interne. Ne comptez pas sur un aria-label placé sur l'élément : le libellé reste sur l'hôte, qui n'a pas de rôle, tandis que le svg interne devient une image sans nom. Sur l'hôte, title est aussi l'attribut HTML global : les navigateurs l'affichent donc sous forme d'infobulle.

index.html
<!-- decorative: the button carries the name -->
<button type="button" aria-label="Close dialog">
  <glyph-fox name="x"></glyph-fox>
</button>

<!-- meaningful on its own -->
<glyph-fox name="triangle-alert" title="Warning" color="#b45309"></glyph-fox>

Les bonnes pratiques pour les liens, les icônes d'état et le contraste sont détaillées dans le guide d'accessibilité.

Rendu côté serveur et décalage de mise en page

L'élément se dessine lors de sa mise à niveau (upgrade) dans le navigateur. Le HTML rendu côté serveur ne contient que la balise vide, et l'élément n'utilise pas le shadow DOM déclaratif : l'icône n'apparaît donc qu'après l'exécution du script. Réservez l'espace nécessaire pour que la mise en page ne bouge pas à ce moment-là.

icons.css
glyph-fox:not(:defined) {
  display: inline-block;
  width: 24px;
  height: 24px;
  vertical-align: middle;
}
glyph-fox[size="16"]:not(:defined) {
  width: 16px;
  height: 16px;
}

Lorsqu'une icône doit être présente dès le premier affichage, dans un e-mail ou dans le HTML destiné aux robots d'indexation, rendez plutôt la chaîne SVG sur le serveur. Les options propres à chaque framework sont présentées dans le guide du rendu côté serveur.

Pièges courants

Balises autofermantes
Écrivez <glyph-fox name="home"></glyph-fox>. Le HTML ignore la barre oblique de <glyph-fox /> : l'élément reste ouvert et le contenu qui le suit devient ses enfants, que le shadow root n'affiche pas.
Liaisons de propriété
Dans Lit, .name=${...} et les autres liaisons de propriété définissent un champ JavaScript que l'élément ne lit pas. Liez des attributs. Solid nécessite attr: pour la même raison (voir le guide Solid).
Attributs vides
Un size vide produit un svg dont la largeur et la hauteur sont vides. Retirez l'attribut lorsqu'il n'a pas de valeur, avec nothing ou ifDefined dans Lit.
Survol et clic sur le parent
Les déclencheurs de survol et de clic ne réagissent que sur l'icône, pas sur le bouton qui l'entoure. Utilisez trigger="state" et définissez active depuis le parent.
Les changements d'attribut rejouent les animations de montage
L'élément remplace son svg interne à chaque changement du balisage : avec un preset animate et le trigger="mount" par défaut, modifier color ou size rejoue donc l'animation. Utilisez trigger="state" pour les icônes dont les autres attributs changent souvent.
Une icône vide
Un nom inconnu ne produit aucun rendu et affiche un avertissement glyphfox: dans la console. Un nom valide mais non enregistré s'affiche dès que son module est chargé : vérifiez donc l'onglet Réseau avant de chercher une faute de frappe.

Questions fréquentes

Puis-je utiliser l'élément dans React, Vue ou Angular ?

Oui, c'est un élément personnalisé standard. Les paquets propres à chaque framework sont toutefois mieux adaptés dans ce cas : glyphfox/react, glyphfox/vue et glyphfox-angular offrent des props typées et prennent en charge le rendu côté serveur. Réservez l'élément à Lit, au HTML pur et aux pages qui mélangent plusieurs stacks. Dans les templates Vue, indiquez au compilateur que glyph-fox est un élément personnalisé avec compilerOptions.isCustomElement, sinon Vue tente de le résoudre comme un composant.

Icônes de ce guide