Frameworks

Icônes dans React Native

React Native ne dispose d'aucun élément SVG intégré : les icônes glyphfox sont donc rendues via react-native-svg. Le moteur glyphfox transforme les données d'une icône en chaîne SVG, que SvgXml dessine nativement sur iOS et Android.

Installation

Installez glyphfox et react-native-svg. Dans un projet Expo, laissez Expo choisir la version de react-native-svg adaptée à votre SDK ; dans un projet bare, installez ensuite les pods iOS.

terminal
npm install glyphfox react-native-svg
# Expo
npx expo install react-native-svg
# bare React Native, iOS
npx pod-install

Les composants React de glyphfox/react ne sont pas destinés à React Native : ils rendent des éléments DOM <svg>, et React Native n'a aucun composant hôte portant ce nom. Utilisez plutôt le moteur principal, décrit ci-dessous.

Fonctionnement

renderToString(icon, options), exporté par glyphfox/core, reçoit les données d'une icône et les props communes, puis renvoie une chaîne SVG complète. Tout ce qui modifie la géométrie se passe en JavaScript, avant même que la chaîne existe : l'axe d'épaisseur et sa compensation optique, les coins arrondis ou vifs, les styles duotone et solid, la rotation et les symétries, les badges de modificateur avec leur espace de découpe, et les fusions. react-native-svg analyse ensuite la chaîne une seule fois et dessine des formes natives.

Ce qui ne passe pas, c'est tout ce qui dépend du CSS d'un navigateur : les animations des états vivants, le morph en CSS pur et la classe de miroir pour l'écriture de droite à gauche. La section sur les limites, plus bas, les recense un par un avec leur équivalent natif.

Un petit composant Glyph

Encapsulez SvgXml une fois pour toutes et utilisez-le partout. La chaîne est mémoïsée : un nouveau rendu du parent ne réanalyse donc pas le SVG, et la couleur est passée à SvgXml sous forme de prop, si bien qu'un changement de thème ne reconstruit pas la chaîne.

Glyph.tsx
import { useMemo } from 'react';
import { SvgXml } from 'react-native-svg';
import { renderToString } from 'glyphfox/core';
import type { IconData, SvgOptions } from 'glyphfox';

type Props = Omit<SvgOptions, 'size' | 'color'> & { icon: IconData; size?: number; color?: string };

export function Glyph({ icon, size = 24, color = '#121014', ...options }: Props) {
  const key = JSON.stringify(options);
  const xml = useMemo(() => renderToString(icon, { ...options, size }), [icon, size, key]);
  return <SvgXml xml={xml} width={size} height={size} color={color} />;
}
Toolbar.tsx
import { View } from 'react-native';
import home from 'glyphfox/icons/home';
import bell from 'glyphfox/icons/bell';
import folder from 'glyphfox/icons/folder';
import { Glyph } from './Glyph';

export function Toolbar() {
  return (
    <View style={{ flexDirection: 'row', gap: 16 }}>
      <Glyph icon={home} size={22} color="#ff7a1a" />
      <Glyph icon={bell} modifier="dot" weight={1.5} />
      <Glyph icon={folder} variant="duotone" />
    </View>
  );
}

Les traits utilisent currentColor, que react-native-svg résout à partir de la prop color. Suivez le thème du système avec useColorScheme() et passez une autre couleur ; la chaîne SVG mise en cache reste la même.

Garder un bundle léger

Metro ne fait pas de tree shaking. Tout ce qu'un module importe se retrouve dans le bundle de l'application, et la manière d'importer les icônes détermine donc le poids embarqué :

ImportContenuÀ utiliser pour
glyphfox/core et glyphfox/icons/NAMELe moteur et les icônes importées, quelques centaines d'octets par icôneLa plupart des applications
toSvg depuis glyphfoxLe moteur et toutes les icônes du jeuPrototypes, ou icônes choisies par leur nom à l'exécution
Un paquet de sous-ensemble généré avec la CLIUniquement des chaînes SVG finales, sans aucun moteurUn ensemble fixe d'icônes aux props fixes

La CLI transforme une liste d'icônes en un petit paquet dont les exports sont de simples chaînes SVG, avec les props figées au moment du build. Ajoutez le dossier à votre application comme dépendance de workspace ou file:, puis passez les chaînes à SvgXml. Les tirets des noms deviennent des tirets bas : arrow-right est donc exporté sous le nom arrow_right :

terminal
npx glyphfox build home search bell arrow-right --type package --name @acme/icons --weight 1.75 --out packages/icons
Header.tsx
import { SvgXml } from 'react-native-svg';
import { home, arrow_right } from '@acme/icons';

export function Header() {
  return (
    <>
      <SvgXml xml={home} width={24} height={24} color="#121014" />
      <SvgXml xml={arrow_right} width={20} height={20} color="#121014" />
    </>
  );
}

Metro et les exports de paquet

glyphfox/core et glyphfox/icons/NAME sont des exports de sous-chemin déclarés dans le champ exports du paquet. Metro lit ce champ par défaut à partir de React Native 0.79. Sur les versions antérieures, activez-le dans la configuration de Metro, sinon ces imports échouent avec une erreur « unable to resolve module » :

metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');

module.exports = mergeConfig(getDefaultConfig(__dirname), {
  resolver: { unstable_enablePackageExports: true },
});

Passez toujours au moteur des données d'icônes, et non des noms. Les paquets web chargent les noms inconnus via un import dynamique du dossier des icônes, ce qui fonctionne avec les bundlers web mais que Metro ne suit pas ; avec les données en main, rien n'a jamais besoin d'être chargé.

Styles, modificateurs et fusions

Les outils de composition du moteur fonctionnent eux aussi sur des données. fuse combine de 2 à 10 icônes en une nouvelle icône qui respecte toujours les règles des icônes, et toutes les options ci-dessous sont appliquées lors de la construction de la chaîne :

Sync.tsx
import { fuse } from 'glyphfox/core';
import cloud from 'glyphfox/icons/cloud';
import lock from 'glyphfox/icons/lock';
import heart from 'glyphfox/icons/heart';
import { Glyph } from './Glyph';

const cloudLock = fuse([cloud, lock], { layout: 'overlap' });

export function Sync({ synced }: { synced: boolean }) {
  return (
    <>
      <Glyph icon={cloudLock} size={28} />
      <Glyph icon={cloud} modifier={synced ? 'check' : 'sync'} modifierCorner="top-right" />
      <Glyph icon={heart} variant="solid" corners="sharp" />
    </>
  );
}

Créez les fusions au niveau du module ou mémoïsez-les : fuse calcule la géométrie de découpe, ce qui ne coûte presque rien une fois, mais devient du gaspillage à chaque rendu.

Animer un morph

Il n'y a pas de CSS pour gérer la transition, mais le moteur de morph lui-même est du JavaScript pur. createMorph(from, to) prépare un morph une seule fois, en utilisant automatiquement les paires ajustées à la main comme menu vers x ou play vers pause, et frame(t) renvoie les données de l'icône pour toute progression comprise entre 0 et 1. tween, fourni par le moteur, pilote la progression avec requestAnimationFrame :

MenuButtonIcon.tsx
import { useEffect, useRef, useState } from 'react';
import { AccessibilityInfo } from 'react-native';
import { SvgXml } from 'react-native-svg';
import { createMorph, renderToString, tween } from 'glyphfox/core';
import menu from 'glyphfox/icons/menu';
import x from 'glyphfox/icons/x';

const morph = createMorph(menu, x);

export function MenuButtonIcon({ open, color = '#121014' }: { open: boolean; color?: string }) {
  const [t, setT] = useState(open ? 1 : 0);
  const current = useRef(t);
  current.current = t;

  useEffect(() => {
    let alive = true;
    let cancel = () => {};
    AccessibilityInfo.isReduceMotionEnabled().then((reduce) => {
      if (alive) cancel = tween(current.current, open ? 1 : 0, { duration: reduce ? 0 : 280 }, setT);
    });
    return () => {
      alive = false;
      cancel();
    };
  }, [open]);

  return <SvgXml xml={renderToString(morph.frame(t), { size: 24 })} width={24} height={24} color={color} />;
}

Chaque frame construit et analyse une nouvelle chaîne, ce qui convient pour une seule icône pendant quelques centaines de millisecondes. Évitez d'exécuter de nombreux morphs simultanément, et confiez plutôt les mouvements prolongés, comme un spinner, à une transformation native d'une vue englobante.

Accessibilité

Les attributs title, role et aria de la chaîne SVG n'atteignent pas l'arbre d'accessibilité natif : définissez donc les props d'accessibilité sur le composant qui entoure l'icône. Une icône placée dans un bouton doté d'un libellé n'a besoin de rien d'autre ; une icône isolée porteuse de sens devient une image avec un libellé :

Status.tsx
import { Pressable, View } from 'react-native';
import search from 'glyphfox/icons/search';
import cloud from 'glyphfox/icons/cloud';
import { Glyph } from './Glyph';

export function Status({ onSearch }: { onSearch: () => void }) {
  return (
    <>
      <Pressable accessibilityRole="button" accessibilityLabel="Search" onPress={onSearch}>
        <Glyph icon={search} />
      </Pressable>
      <View accessible accessibilityRole="image" accessibilityLabel="All changes synced">
        <Glyph icon={cloud} modifier="check" />
      </View>
    </>
  );
}

Les règles générales qui indiquent quand une icône a besoin d'un libellé se trouvent dans le guide d'accessibilité.

Ce qui ne se transpose pas

FonctionnalitéSur le webDans React Native
États vivants (animate)Des keyframes dans un élément <style> au sein du SVGSvgXml ignore l'élément style. Animez une vue englobante avec Animated ou Reanimated, par exemple une rotation pour un spinner.
Morph en CSS pur (morphTo avec un déclencheur)Une transition CSS des données de tracéRendez des frames avec createMorph, comme montré plus haut.
mirrorRtlUne classe qui retourne l'icône dans un contexte dir="rtl"Passez flipX={I18nManager.isRTL} aux icônes directionnelles.
title et decorativeUn élément title, role="img" et aria-hiddenProps d'accessibilité sur le composant englobant.
Tailles relatives"1em", "2rem"Nombres uniquement.

Props

Ces options de renderToString et toSvg modifient le dessin lui-même et fonctionnent dans React Native. Elles portent les mêmes noms et ont la même signification que les props de tous les autres paquets glyphfox.

OptionTypePar défautDescription
sizenumber, string24Largeur et hauteur ; les nombres sont en pixels.
colorstringcurrentColorCouleur principale.
secondaryOpacitynumber0.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.
absoluteWeightbooleanfalseGarde 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.
flipXbooleanfalseMiroir horizontal.
flipYbooleanfalseMiroir vertical.
modifierplus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dotaucunBadge avec espace de découpe automatique.
modifierCornertop-left, top-right, bottom-left, bottom-rightbottom-rightCoin du badge.

Pièges courants

Utiliser glyphfox/react
Les composants pour le web rendent des éléments DOM : React Native s'arrête donc sur une erreur indiquant qu'aucune view config n'existe pour svg. Passez plutôt par SvgXml.
Tout le jeu d'icônes dans le bundle
import { toSvg } from 'glyphfox' embarque toutes les icônes dans l'application, car Metro conserve l'intégralité du paquet. Importez les données des icônes une par une, ou générez un paquet de sous-ensemble.
Impossible de résoudre glyphfox/core
Avant React Native 0.79, la prise en charge des exports de paquet est désactivée dans Metro. Activez unstable_enablePackageExports.
Analyse à chaque rendu
Un appel inline à renderToString dans une longue liste construit et analyse une chaîne par ligne et par rendu. Mémoïsez la chaîne, comme le fait le composant Glyph, ou utilisez des chaînes pré-générées.
Noms inconnus
toSvg lève une erreur pour un nom qui n'existe pas. Validez les noms provenant d'un serveur à l'aide de iconNames, ou faites vous-même la correspondance avec les données importées.

Questions fréquentes

Est-ce compatible avec Expo ?

Oui. Installez react-native-svg avec npx expo install react-native-svg et utilisez le même composant Glyph. Expo utilise Metro, la remarque sur les exports de paquet s'applique donc : les SDK basés sur React Native 0.79 ou une version plus récente ne nécessitent aucune modification.

Comment créer un spinner de chargement ?

Dessinez loader ou loader-circle avec le composant Glyph, puis faites tourner une Animated.View englobante avec une animation timing en boucle. Le native driver assure sa fluidité, et la chaîne SVG n'est construite qu'une seule fois.

Icônes de ce guide