Créer une librairie d’icônes avec React, Next.js et Typescript

Publié le 13 février 2024
Baptiste Drillien avatar
Baptiste Drillien
Développeur front-end

Une icône est un symbole que l’on trouve dans les interfaces utilisateur (UI). Elles sont universelles et respectent des conventions. Contrairement aux images matricielles, les icônes sont vectorielles.

Elles peuvent être redimensionnées sans perte de qualité, ce qui les rend facilement responsives.

Les icônes sont largement utilisées pour représenter des actions ou des états. Elles participent à l’affordance d’une interface et donc à l’UX d’un produit, en plus de renforcer l’identité visuelle.

Leur pertinence n’est pas à prouver et la question du choix de la librairie d’icônes se fait pour chaque projet. Dans cet article, nous allons voir pourquoi il est préférable de créer sa propre librairie.

Pourquoi créer une librairie d’icônes sur mesure ?

Bien qu’il existe de nombreuses librairies d’icônes gratuites et facilement utilisables telles que Material Symbols (Google) ou encore Font Awesome, créer une librairie sur mesure présente plusieurs avantages :

  • Droits d'auteur et licences : aucun problème avec une librairie créée sur mesure.
  • Optimisation : inutile d’installer des centaines d’icônes inutilisées.
  • Maintenabilité : il est facile de modifier des icônes, d’en ajouter et d’en supprimer.
  • Meilleure accessibilité : c’est une mauvaise pratique d’utiliser une police pour ses icônes.
  • Flexibilité : il est possible d’utiliser des icônes provenant de différentes librairies existantes, en plus d’en créer soi-même.

Créer la librairie d’icônes

Créons ensemble une librairie composée de 10 icônes que l’on retrouve couramment dans des applications web.

Pour cela, nous allons créer dans un premier temps un projet React / Next.js avec Typescript et TailwindCSS (qui est optionnel).

Icons example preview

Création du composant de l’icône

Imaginons que nous sommes en train d’intégrer une maquette fournie par un designer. Première étape, il faut exporter l’icône en svg.

Pour pouvoir l’utiliser dans notre projet avec React & Next.js, il faut convertir notre svg, qui est construit sur une base de XML en jsx ou, dans notre cas, en tsx.

Si notre svg est uniquement utilisé avec des couleurs prédéfinies , il n’est pas nécessaire de modifier les fill et / ou stroke.

En revanche, dans la mesure ou notre icône peut être utilisée dans différentes couleurs, le plus simple est de lui attribuer la même couleur que pour le texte. Il suffit de remplacer la valeur de la couleur par currentColor.

bell.tsx
1export const Bell = () => (
2 <svg
3 xmlns="http://www.w3.org/2000/svg"
4 fill="none"
5 viewBox="0 0 24 24"
6 strokeWidth={1.5}
7 stroke="currentColor"
8 width={24}
9 height={24}
10 >
11 <path
12 strokeLinecap="round"
13 strokeLinejoin="round"
14 d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"
15 />
16 </svg>
17);

Utiliser notre nouvelle icône dans le projet

Pour éviter d’avoir à importer un composant pour chaque icône, et permettre de typer les props à chaque fois sans dupliquer du code, il convient de passer par un index pour exporter nos icônes.

Comment importer nos icônes ?

Grâce à notre index, nous pouvons éviter d’avoir des imports trop conséquents, comme par exemple :

page.tsx
1import { ArrowDownTray } from "@/ui/components/icons/arrow-down-tray";
2import { Bell } from "@/ui/components/icons/bell";
3import { ChatBubble } from "@/ui/components/icons/chat-bubble";
4import { Check } from "@/ui/components/icons/check";
5import { Cog } from "@/ui/components/icons/cog";
6import { Home } from "@/ui/components/icons/home";
7import { Lock } from "@/ui/components/icons/lock";
8import { MagnifyingGlass } from "@/ui/components/icons/magnifying-glass";
9import { PencilSquare } from "@/ui/components/icons/pencil-square";
10import { Trash } from "@/ui/components/icons/trash";
11import { User } from "@/ui/components/icons/user";
12import { XMark } from "@/ui/components/icons/x-mark";

Mais également des imports à rallonge :

page.tsx
1import {
2 ArrowDownTray,
3 Bell,
4 ChatBubble,
5 Check,
6 Cog,
7 Home,
8 Lock,
9 MagnifyingGlass,
10 PencilSquare,
11 Trash,
12 User,
13 XMark,
14} from "@/ui/components/icons";

Nous pouvons simplement utiliser un composant unique sous cette forme :

page.tsx
1import { Icon } from "@/ui/components/icons";
2
3const Page = () => (
4 <main>
5 <Icon name="bell" />
6 </main>
7)
Consulter le code
Le code de cet article est disponible, vous pouvez le consulter sur GitHub.

Création du composant index

index.tsx
1import { SVGProps } from "react";
2import { ArrowDownTray } from "@/ui/components/icons/arrow-down-tray";
3import { Bell } from "@/ui/components/icons/bell";
4import { ChatBubble } from "@/ui/components/icons/chat-bubble";
5import { Check } from "@/ui/components/icons/check";
6import { Cog } from "@/ui/components/icons/cog";
7import { Home } from "@/ui/components/icons/home";
8import { Lock } from "@/ui/components/icons/lock";
9import { MagnifyingGlass } from "@/ui/components/icons/magnifying-glass";
10import { PencilSquare } from "@/ui/components/icons/pencil-square";
11import { Trash } from "@/ui/components/icons/trash";
12import { User } from "@/ui/components/icons/user";
13import { XMark } from "@/ui/components/icons/x-mark";
14
15const ICONS = {
16 "arrow-down-tray": ArrowDownTray,
17 bell: Bell,
18 "chat-bubble": ChatBubble,
19 check: Check,
20 cog: Cog,
21 home: Home,
22 lock: Lock,
23 "magnifying-glass": MagnifyingGlass,
24 "pencil-square": PencilSquare,
25 trash: Trash,
26 user: User,
27 "x-mark": XMark,
28};
29
30type Props = {
31 name: keyof typeof ICONS;
32 size?: 16 | 24 | 32 | 48;
33} & SVGProps<SVGSVGElement>;
34
35export const Icon = ({ name, size = 24, ...rest }: Props) => {
36 const Component = ICONS[name];
37 return <Component height={size} width={size} {...rest} />;
38};

Notre composant Icon est correctement typé et il est facile de changer la taille des icônes. Il est également possible d’utiliser tailwind via les classNames pour modifier la taille.

hexa web logo
Hexa web
Des conseils pour un projet web ?
Nous contacter

De plus, chaque composant d’icône est typé, et peut prendre les props du composant Icon.

bell.tsx
1import { SVGProps } from "react";
2
3type Props = SVGProps<SVGSVGElement>;
4
5export const Bell = ({ ...props }: Props) => (
6 <svg
7 xmlns="http://www.w3.org/2000/svg"
8 fill="none"
9 viewBox="0 0 24 24"
10 strokeWidth={1.5}
11 stroke="currentColor"
12 {...props}
13 >
14 <path
15 strokeLinecap="round"
16 strokeLinejoin="round"
17 d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"
18 />
19 </svg>
20);

Il est donc facile de modifier chaque icône selon le besoin via le composant parent Icon. On peut par exemple imaginer utiliser TailwindCSS au lieu de passer par une size.

index.tsx
1export const Icon = ({ name, className, ...rest }: Props) => {
2 const Component = ICONS[name];
3 return <Component className={twMerge("h-4 w-4", className)} {...rest} />;
4};

Conclusion

La création d'une librairie d'icônes sur mesure avec React, Next.js et Typescript offre une meilleure optimisation, une maintenance simplifiée et une importante flexibilité : on a un contrôle total sur nos icônes.

Pour tirer profit au maximum de sa librairie, il est possible de la documenter avec Storybook ou encore de la publier en tant que package npm.

Consulter le code
Le code de cet article est disponible, vous pouvez le consulter sur GitHub.

Échangeons sur votre projet web

Présentez-nous votre projet web, nous vous recontacterons dans les prochaines 24h.