Baptiste Drillien
Baptiste Drillien

Développeur Next.js - Freelance

Disponible - Me contacter

Créer une librairie d'icônes avec Next.js et TypeScript

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 :

Créer la librairie d'icônes

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

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

Exemple de librairie d'icônes
Exemple de librairie d'icônes

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 le projet avec React & Next.js, il faut convertir le svg, qui est construit sur une base de XML en jsx ou, dans ce cas, en tsx.

Si le 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 où l'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
export const Bell = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
width={24}
height={24}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
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"
/>
</svg>
);

baptistedri/custom-icons-library

Exemple de librairie d'icônes avec React, Next.js et Typescript

Utiliser la 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 les icônes.

Comment importer les icônes ?

Grâce à l'index, on peut éviter d'avoir des imports trop conséquents, comme par exemple :

page.tsx
import { ArrowDownTray } from "@/ui/components/icons/arrow-down-tray";
import { Bell } from "@/ui/components/icons/bell";
import { ChatBubble } from "@/ui/components/icons/chat-bubble";
import { Check } from "@/ui/components/icons/check";
import { Cog } from "@/ui/components/icons/cog";
import { Home } from "@/ui/components/icons/home";
import { Lock } from "@/ui/components/icons/lock";
import { MagnifyingGlass } from "@/ui/components/icons/magnifying-glass";
import { PencilSquare } from "@/ui/components/icons/pencil-square";
import { Trash } from "@/ui/components/icons/trash";
import { User } from "@/ui/components/icons/user";
import { XMark } from "@/ui/components/icons/x-mark";

Mais également des imports à rallonge :

page.tsx
import {
ArrowDownTray,
Bell,
ChatBubble,
Check,
Cog,
Home,
Lock,
MagnifyingGlass,
PencilSquare,
Trash,
User,
XMark,
} from "@/ui/components/icons";

On peut simplement utiliser un composant unique sous cette forme :

page.tsx
import { Icon } from "@/ui/components/icons";

const Page = () => (
<main>
<Icon name="bell" />
</main>
);

Création du composant Icon

icon.tsx
import { SVGProps } from "react";
import { ArrowDownTray } from "@/ui/components/icons/arrow-down-tray";
import { Bell } from "@/ui/components/icons/bell";
import { ChatBubble } from "@/ui/components/icons/chat-bubble";
import { Check } from "@/ui/components/icons/check";
import { Cog } from "@/ui/components/icons/cog";
import { Home } from "@/ui/components/icons/home";
import { Lock } from "@/ui/components/icons/lock";
import { MagnifyingGlass } from "@/ui/components/icons/magnifying-glass";
import { PencilSquare } from "@/ui/components/icons/pencil-square";
import { Trash } from "@/ui/components/icons/trash";
import { User } from "@/ui/components/icons/user";
import { XMark } from "@/ui/components/icons/x-mark";

const ICONS = {
"arrow-down-tray": ArrowDownTray,
bell: Bell,
"chat-bubble": ChatBubble,
check: Check,
cog: Cog,
home: Home,
lock: Lock,
"magnifying-glass": MagnifyingGlass,
"pencil-square": PencilSquare,
trash: Trash,
user: User,
"x-mark": XMark,
};

type Props = {
name: keyof typeof ICONS;
size?: 16 | 24 | 32 | 48;
} & SVGProps<SVGSVGElement>;

export const Icon = ({ name, size = 24, ...rest }: Props) => {
const Component = ICONS[name];
return <Component height={size} width={size} {...rest} />;
};

Le 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.

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

bell.tsx
import { SVGProps } from "react";

type Props = SVGProps<SVGSVGElement>;

export const Bell = ({ ...props }: Props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
{...props}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
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"
/>
</svg>
);

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.

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

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 les 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.

baptistedri/custom-icons-library

Exemple de librairie d'icônes avec React, Next.js et Typescript