Un design system avec Figma et React
1/10/2024 - 5 min. de lecture
Lors de la conception d’un design system ou d’un kit UI, le passage d’information entre designers et développeurs est une étape essentielle. Un exemple concret, ici, d’un passage de Figma à React.
Construction
Le principal avantage de Figma sont les propriétés de composant. Utilisons les.

La structure du composant ressemble donc à cela.
// Composant Bouton dans Figma
Button : {
State : ["Default", "Hover", "Disabled"],
Type : ["Primary", "Secondary", "Destructive",
"Outlined", "Ghost", "Link"],
Size: ["sm", "md", "lg"],
Icon: Boolean,
IconType: Component,
Label: String
}
Lors de la “traduction” vers React, on peut réutiliser le gros des propriétés. Toutefois, ce n’est pas un décalque. L’affichage conditionnel dans Figma est limité à un booléen (propriétés avec true | false
. Voici la structure adaptée à React (et à HTML).
// Composant Bouton dans React
Button : {
State : ["Default", "Hover", "Disabled"], // Définit par HTML, ciblé avec CSS
Type : ["Primary", "Secondary", "Destructive", "Outlined", "Ghost", "Link"], // CVA
Size: ["sm", "md", "lg"], // CVA
IconType: Component, // Référence à un autre composant, si absent, pas d'icone
Label: String // Passé comme argument au composant : idem que pour l'icone, sa présence peut conditionner l'affichage
}
Le composant est initialisé avec quelques props, et CVA est utilisé pour gérer les états.
export type ButtonProps = React.HTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants> & {
label: string;
Icon?: React.ComponentType<IconProps>;
}
const buttonVariants = cva("button transition ...", {
variants: {
type: {
primary: [
"bg-button-bg-color-filled",
"text-button-text-color-filled",
...
],
secondary: [
"bg-none",
"text-button-text-color-outline",
"border-button-border-color-outline",
"border",
"text-button-text-color-outline",
...
],
destructive: [
"bg-button-bg-color-destructive",
"text-button-text-color-destructive",
...
],
ghost: [
...
],
...
},
size: {
sm: ["text-sm", "px-2", "gap-1", "h-6"],
md: ["text-base", "px-3", "gap-2", "h-8"],
lg: ["text-lg", "px-4", "gap-2", "h-10"],
},
},
});

Le composant est construit, il peut être appelé :
<Button label="Supprimer" type="destructive" Icon={Trash} size="md" />
React offre la liberté de construire le composant comme on le souhaite. On peut donc utiliser des enfants plutôt que des attributs.
<Button type="destructive" size="md">
<Trash size=16/>
Supprimer
</Button>
La première solution aurait ma faveur. Les paramètres sont exposés, l’intégration ne se soucie pas de la structure visuelle : l’icone est avant le nom, ou inverse. Si la position doit varier, on exposera un paramètre. Quelque chose comme iconPosition: after|before
, par exemple. On sépare ainsi les responsabilités, et les décisions d’affichage sont gérées à l’intérieur du composant. Lors de l’appel, on ne fait que configurer le composant.
Mais à vrai dire, je n’ai pas de chapelle. C’est un bel exemple de la loi de Tesler de la complexité conservative. Il s’agira donc de savoir où placer cette complexité : dans son appel ? Dans le coeur du composant ? Quelle que soit la façon choisie par le développeur front pour les créer et les maintenir, il faut s’accorder sur une façon de transférer les informations. Et de la documenter (et c’est un gros sujet, qui n’est pas l’objet de cet article).
Plus complexe : un élément de liste

D’abord, il faut définir comment on souhaite que le composant soit appelé. Un exemple (avec les états par défaut).
<ListItem
Type="Text" // Par défaut : Text
Label="Label" // Par défaut : Label
Rounded={false} // Par défaut : false
DetailsDisplay="NewLine" // Par défaut : Newline
Size="md" // Par défaut : md
Icon={Cog} // Par défaut : Null
Checkbox={true} // Par défaut : false
Pill="00" // Par défaut : null
Favorite={false} // Par défaut : false
Kbd="⇧ ⌘ K" // Par défaut : null
/>
Ce qui permet de monter un menu de la sorte
<List>
<ListItem DetailsDisplay="NewLine" Size="md" Icon={Screen} Icon={Archive} />
<ListItem Label="Files" Icon={File} />
<ListItem Label="Another option">
<ListItem Label="Notifications" Icon={Bell} Pill="3" />
<ListItem Type="Separator" />
<ListItem Label="User settings" Icon={Cog} DetailsDisplay="InlineAfter" Details="Your name" />
<ListItem Label="Logout" Icon={Logout} />
</List>
Il y a plus de décisions de design à prendre ici (dans la conception et dans le code). Avec toujours le même constat : la communication est la clé.

Conclusion
On le voit, lors de la construction d’un design system, un effort de traduction est nécessaire pour transférer les composants du logiciel graphique au code. Cette grille de correspondance fera l’objet de la plus grande attention de la part des développeurs et des designers. Initier ce dialogue au plus tôt permet de définir des modalités partagées entre tous les participants.
Car on le sait : plus encore que sa construction, c’est l’appropriation et la maintenance d’un design system qui sera cruciale, et qui déterminera son impact sur les produits de l’entreprise.