Un design system avec Figma et React

1/10/2024 - 5 min. de lecture

Lors de la conception d’un design system, 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.

Vue des propriétés du composant Bouton dans Figma
Les propriétés du composant dans Figma

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
}

Différences notables entre Figma et React

Avec React, on peut activer une props en donnant une valeur (string ou quoi que ce soit d’autre). Dans Figma, on doit d’abord afficher ou masquer l’objet graphique, puis lui donner une valeur. Le nombre d’attributs augmente, de fait.

React permet aussi de passer des composants en paramètres, ou en tant qu’enfant. Ce que ne permet pas directement Figma.

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"],
        },
    },
});
Vue des propriétés du composant Bouton dans Figma
Le résultat vu dans Storybook. Le composant est maintenant créé, on peut tester les différents configurations possibles, comme dans Figma. Mais le composant vit, désormais

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

Vue des propriétés du composant Bouton dans Figma
L'élément de base de la liste vu dans Figma. Un seul élément couvre tous les cas de figure : on remarque le nombre de paramètres disponibles pour tous les couvrir

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

Vue des propriétés du composant Bouton dans Figma
Un composant bien pensé permet de créer rapidement un menu comportant beaucoup de paramètres d'affichage. D'où l'importance de prendre le temps de bien construire le composant

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.