Le web mérite de meilleures primitives

zoontek
zoontek 2021/04/12

Il est souvent reproché aux développeurs front-end d'utiliser des abstractions inutilement complexes sous couvert d'une volonté de "s'amuser" avec celles-ci plutôt que de concevoir des sites simples, accessibles et performants.

Personnellement, j'apprécie énormément react-native-web et ai tendance à m'en servir systématiquement et ce même si la plupart du temps il n'est même pas question de partage de code avec une application mobile.

Ce qui veut dire que je suis régulièrement tenu d'écrire du code de la sorte:

import { Pressable } from "react-native";

<Pressable
  accessibilityRole="button"
  style={({ hovered, focused, pressed }) => [
    { backgroundColor: "red" },
    hovered && { backgroundColor: "blue" },
    focused && { backgroundColor: "green" },
    pressed && { backgroundColor: "pink" },
  ]}
>
  Click me
</Pressable>;

Vous allez me dire que c'est horrible. J'utilise une <div> en lieu et place d'un <button>, du CSS-in-JS pour styliser mon élément et je fais même totalement l'impasse sur l'usage des pseudo-classes :hover et :focus!

Pourquoi diable s'acharner à réimplémenter ce qui existe de base sur la plateforme web et qui fonctionne bien?

Qui fonctionne bien, vous êtes sûrs?

Déconstruisons donc cet exemple. Je souhaite créer un bouton rouge qui sera bleu au survol, vert quand il aura le focus et rose quand il sera pressé.

Le cas du survol

Implémentons notre solution naïvement:

<button>Click me</button>
button {
  appearance: none;
  background-color: red;
}

button:hover {
  background-color: blue;
}

Cela ne suffira pas. En effet, mon bouton deviendra bleu lors d'un appui sur smartphone ou autre appareil tactile.

Bouton bleu

Heureusement, il existe une bride de solution: le media query hover.

hover est une caractéristique média CSS (cf. @media) qui permet de vérifier si le dispositif de saisie/d'entrée principal permet à l'utilisateur de survoler les éléments.

Il serait donc possible de corriger le problème de la sorte:

button {
  appearance: none;
  background-color: red;
}

@media (hover: hover) {
  button:hover {
    background-color: blue;
  }
}

Sauf qu'encore une fois, tout cela reste imparfait. Depuis peu, il est devenu possible d'utiliser un périphérique de pointage (type souris donc, avec son curseur) sur iPad et, je vous le donne en mille, le dispositif d'entrée principal restant l'écran tactile, les styles présents dans le media query en question ne seront pas activés lors du survol du curseur.

L'affaire serait donc insoluble? Pas nécessairement, mais résoudre tous les problèmes inhérents à chaque appareil et situation afin de normaliser le comportement de cette interaction pourtant basique n'est au final pas mince affaire, comme peuvent l'attester l'implémentation des abstractions offertes par react-native-web ou @react-aria.

Le cas du focus

Rebelote, traitons le problème naïvement:

button {
  appearance: none;
  background-color: red;
}

button:focus {
  background-color: green;
}

Tout comme pour le cas du survol, cette solution est loin d'être parfaite puisque le style appliqué normalement au focus sera visible au clic.

Bouton vert

Pire encore, l'apparition du focus-ring, pourtant indispensable à une bonne navigation au clavier, poussera grand nombre de développeurs non sensibilisés au sujet à appliquer le fameux outline-style: none et à anéantir une grande part de l'accessibilité du site au passage.

Mais tout comme le cas du survol, il existe une bride de solution: la pseudo-classe :focus-visible.

La pseudo-classe :focus-visible s'applique lorsqu'un élément correspond à la pseudo-classe focus et que l'agent utilisateur détermine, via une heuristique, que le focus devrait être mis en évidence sur l'élément (la plupart des navigateurs affichent un contour en surbrillance par défaut).

button {
  appearance: none;
  background-color: red;
}

button:focus-visible {
  background-color: green;
}

Et tout comme le media query hover, cette solution reste imparfaite: si l'on met de côté son support navigateur restreint à l'heure où je rédige cet article, on peut également pointer du doigt le fait que le focus reste "cassé" en JavaScript. En effet, il n'est pas encore question d'un événément focus-visible et l'événement focus reste déclenché lors du clic sur certains navigateurs.

Il vous sera donc très difficile de réagir à une réelle intention de focus, sauf si, encore une fois, vous utilisez des abstractions offertes par react-native-web, @react-aria ou autre.

Le cas du <button>

Reste un dernier point: l'usage d'une <div> à la place d'un <button>. L'argument souvent avancé contre cela est le suivant :

Utiliser une <div> pour réimplémenter un bouton en lieu et place d'un <button> peut ruiner l'accessibilité de celui-ci.

Je suis absolument d'accord avec ce point. Réimplémenter le comportement d'un bouton en partant de zéro est complexe, vous mènera certainement à commettre un tas d'erreurs et je vous le déconseille fortement.

Seulement ici ce n'est pas le cas : j'utilise une abstraction qui a correctement été pensée et ne souffre d'aucun réel défaut d'accessibilité si on la compare à un <button>. Sachant qu'en plus il y a encore très peu, les <button> souffraient de problèmes de styling et que donc, il m'est nécessaire soit d'en tenir compte, soit de ne pas supporter certaines versions de navigateurs pourtant récentes.

Si j'ai ce composant <Pressable> à ma disposition, qui a le bon goût de ne pas souffrir des problèmes présentés plus haut et réagit tel que la logique le voudrait lors du survol, du focus ou encore de l'appui… Pourquoi devrais-je donc m'en passer?

Autres manques, en vrac

Le focus-trapping

Afin de faciliter l'accessibilité clavier, il est important de "capturer" et de borner le focus lors de l'apparition d'une modale, par exemple (ce qu'on appelle communément le focus-trapping).

Exemple d'échec de focus trapping: il est possible de sélectionner le champ de recherche à l'aide de son clavier

Il existe actuellement 2 potentielles solutions à ce problème. L'une, haut niveau, est l'élément natif <dialog>, l'autre, plus bas niveau (et plus intéressante selon moi) est l'attribut inert. Ces solutions ne sont malheureusement pour le moment pas très bien, voire pas du tout, supportées par les navigateurs.

Les popups

Tout comme les modales, elles sont présentes dans une majeure partie des interfaces web actuelles et pourtant il n'existe actuellement aucune méthode simple et sans abstraction qui nous permette d'en créer une.

Dans un futur proche, peut-être (je l'espère) que la proposition de Microsoft sera retenue, mais en attendant, bon courage pour implémenter ça "à la main" si vous souhaitez que celles-ci soient fonctionnelles sur tous les navigateurs, tout type d'appareil et de surcroît parfaitement accessibles.

Plus? Plus!

Qu'en est-il de tout ces besoins devenus quasiment primaires:

  • Les masques sur les champs texte?
  • Un élément <switch>?
  • Les listes virtualisées (avec recherche fonctionnelle au sein de la page)?
  • Le RTL (right-to-left) simplifié?
  • Les containers queries en CSS?
  • Le champ texte type=number (actuellement cassé)?
  • … la liste est potentiellement infinie

Du coup, qu'est ce qu'on fait?

Avant tout, arrêtons de blâmer les développeurs qui découvrent aujourd'hui cette complexité monstre et cessons de leur déconseiller d'utiliser des abstractions, de leur dire de "juste apprendre le HTML, CSS et JS" car ce comportement est dangereux et ne tient pas compte d'une chose : si vous avez vu vécu l'évolution du web et vu apparaitre des courants tels que le responsive design, de nouveaux types d'appareils tels que les smartphones et eu le temps de vous adapter à ces changements, ce n'est pas le cas de développeurs plus juniors qui se retrouvent aujourd'hui face à une quantité astronomique de problématiques, sans courbe d'apprentissage naturelle guidée par l'évolution des usages.

L'accessibilité vous tient à cœur? Parfait. Conseillez-leur d'utiliser une abstraction simple qui leur garantira un bon résultat, plutôt que de leur conseiller de devenir un expert absolu sur le domaine afin de ne pas se tirer une balle dans le pied. S'il sont intéressés par le sujet, ils se pencheront sur la façon dont est conçue celle-ci, s'ils ne le sont pas, et bien, au moins les utilisateurs de leurs sites bénéficieront d'une meilleure accessibilité. Et je pense que c'est ce qui importe le plus: l'objectif est plus important que le moyen.

Ensuite, posons-nous réellement la question de la nécessité de ces ajouts continuels de solutions de haut niveau dans la plateforme web, qui débarquent sans que jamais rien ne semble vraiment être officiellement déclaré caduque. En effet, toutes les solutions énoncées précédemment viennent ou viendront en plus de tout ce qui peut déjà exister.

La plateforme déborde. J'en prends pour exemple une donnée qui me semble totalement absurde, le nombre de propriétés CSS actuel: 520 (+132 en attente). Outre le fait que cela rend extrêmement complexe l'apparition de nouveaux navigateurs web, cela rend la charge mentale du développeur incommensurable. Il en va de même avec l'évolution de la syntaxe JavaScript, l'augmentation du nombre de sélecteurs CSS, l'apparition de nouvelles APIs, etc.

Imaginez si demain un nouveau paradigme d'utilisation venait à apparaitre (une interaction autre que le touch, par exemple) ou si un nouveau type de pattern UI gagnait en popularité (comme a pu le faire le switch). Devrait-on continuer à empiler…? Ou devrait-on accepter le fait que le web a plus que jamais besoin d'abstractions au dessus de concepts bas niveau, que celles-ci soient des bibliothèques exécutées au runtime ou carrément des langages de plus haut niveau qui compileront vers du HTML, CSS et JS en guise de bytecode?

L'avenir est incertain, imitez-le.

Vous avez aimé cet article?
Le partager sur Twitter
← Articles
Ne rien rater
Sur les réseaux
Twitter
Facebook
Facebook
Apple Podcast
Soundcloud
Sur le chat
Discord