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.
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.
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} />; }
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é :
| Import | Contenu | À utiliser pour |
|---|---|---|
glyphfox/core et glyphfox/icons/NAME | Le moteur et les icônes importées, quelques centaines d'octets par icône | La plupart des applications |
toSvg depuis glyphfox | Le moteur et toutes les icônes du jeu | Prototypes, ou icônes choisies par leur nom à l'exécution |
| Un paquet de sous-ensemble généré avec la CLI | Uniquement des chaînes SVG finales, sans aucun moteur | Un 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 :
npx glyphfox build home search bell arrow-right --type package --name @acme/icons --weight 1.75 --out packages/icons
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 » :
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 :
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 :
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é :
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 web | Dans React Native |
|---|---|---|
États vivants (animate) | Des keyframes dans un élément <style> au sein du SVG | SvgXml 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. |
mirrorRtl | Une classe qui retourne l'icône dans un contexte dir="rtl" | Passez flipX={I18nManager.isRTL} aux icônes directionnelles. |
title et decorative | Un élément title, role="img" et aria-hidden | Props 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.
| Option | Type | Par défaut | Description |
|---|---|---|---|
size | number, string | 24 | Largeur et hauteur ; les nombres sont en pixels. |
color | string | currentColor | Couleur principale. |
secondaryOpacity | number | 0.3 / 1 | Opacité du calque duotone ou du remplissage plein. |
weight | number | 2 | Trait de 1 à 3 sur la grille de 24, avec compensation optique en dessous de 24 px. |
absoluteWeight | boolean | false | Garde le trait à weight pixels quelle que soit la taille. |
corners | round, sharp | round | Jointures et extrémités arrondies ou vives. |
variant | outline, duotone, solid | outline | Axe de style ; solid vient des formes fermées. |
rotate | number | 0 | Rotation en degrés. |
flipX | boolean | false | Miroir horizontal. |
flipY | boolean | false | Miroir vertical. |
modifier | plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dot | aucun | Badge avec espace de découpe automatique. |
modifierCorner | top-left, top-right, bottom-left, bottom-right | bottom-right | Coin 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 parSvgXml. - 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 à
renderToStringdans 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
toSvglève une erreur pour un nom qui n'existe pas. Validez les noms provenant d'un serveur à l'aide deiconNames, 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.