Les composants Web sont des éléments personnalisés, des composants Shadow DOM et des modèles HTML : les primitives de la plate-forme pour les composants réutilisables et indépendants du framework dont le balisage et les styles sont encapsulés dans le document. Utilisez-les pour les systèmes de conception, les widgets intégrables et l'interface utilisateur autonome.
Les règles ci-dessous sont transversales. Récupérez le guide spécifique à la tâche à accomplir. Chacun d'eux rappelle le contexte dont il a besoin et documente les API et les pièges qui lui sont propres.
Déterminer si vous avez besoin d'un élément personnalisé
N'utilisez un élément personnalisé que lorsque vous avez besoin d'un comportement encapsulé, d'un cycle de vie ou d'une API réutilisable basée sur JS sur une balise. Ne créez pas de modèle dans les cas suivants :
- Un élément HTML simple avec CSS ferait l'affaire. Un élément
<button>ou<details>stylisé est plus accessible et moins coûteux qu'une réimplémentation. - L'élément est purement visuel et n'a aucun comportement. Une classe sur un élément natif est plus performante et peut être limitée avec
@layerou@scope. - Vous n'avez besoin de définir la portée du style que sur le serveur, sans interactivité. Une feuille de style limitée ou un Shadow DOM déclaratif (sans mise à niveau) peuvent suffire.
La reconstruction de la sémantique native (un <my-button> personnalisé qui recrée la sélection, l'activation et ARIA) est l'utilisation abusive la plus courante : vous n'héritez de l'accessibilité de la plate-forme sans frais que si vous réutilisez l'élément natif. Lorsqu'un wrapper de style <my-button> est justifié (généralement pour des hooks de style partagés, un balisage supplémentaire ou un comportement qu'un élément natif ne peut pas exprimer), encapsulez le véritable <button> plutôt que de le réimplémenter (consultez Styliser des composants Web pour transférer des styles vers un élément natif interne).
Pour savoir si vous devez utiliser un élément personnalisé, demandez-vous si vous avez besoin d'un état encapsulé, d'une API scriptable ou d'un comportement que l'élément natif ne peut pas exprimer, et pas seulement d'une apparence différente. Un bouton J'aime/Favoris est un bon exemple : il comporte un état de domaine verrouillé, expose cet état en tant que hook de style pour la page environnante et distribue son propre événement change, ce qu'un <button> stylisé ne vous permet pas de faire. Un bouton qui ne change de couleur que lorsqu'on appuie dessus n'est pas suffisant : utilisez <button> + CSS. Lorsque vous le créez, modélisez son état avec un état ou un attribut personnalisé, selon le cas (voir Éléments personnalisés).
Adapter l'outil au codebase
La décision de créer un élément personnalisé ou non dépend de ce que le projet utilise déjà. Les agents ont tendance à trop abstraire et à rechercher un nouvel élément trop rapidement. En cas de doute, l'option la plus légère (élément natif, CSS, modèle de serveur) est généralement la bonne, et vous pouvez passer à un élément personnalisé plus tard si la réutilisation ou le comportement l'exigent. Heuristiques, par ordre de priorité approximatif :
- Le codebase dispose déjà d'un modèle de composant (React, Vue, Svelte, etc.). Utilisez par défaut le composant de ce framework pour l'UI intégrée à l'application. N'utilisez un élément personnalisé que lorsque l'élément doit échapper au framework ou lui survivre : une primitive de système de conception partagée entre des applications ou des frameworks, un widget inséré dans un balisage que le framework ne contrôle pas, ou une sortie qui doit fonctionner en HTML brut (e-mail, CMS, fragments SSR). Ne créez pas de système de composants parallèle pour les vues standards dans l'application.
- Aucun outil, balisage principalement statique. Privilégiez le code HTML et CSS brut, ou un modèle/partiel de serveur. Un élément personnalisé n'est utile que si vous avez réellement besoin d'un comportement, d'un cycle de vie ou d'une API basée sur JS sur la balise, et pas seulement pour partager un balisage, ce qu'un serveur inclut ou
<template>fait sans envoyer de JS. - Générateur de site statique avec des îlots d'interactivité. Affichez le balisage de manière statique et réservez l'élément personnalisé pour la partie interactive. Pour "beaucoup de balisage, un peu d'interactivité", conservez le balisage dans le Light DOM (ou le Shadow DOM déclaratif) et laissez un petit élément personnalisé l'améliorer progressivement, plutôt que de tout afficher à nouveau à partir de JS.
- Vous pouvez plutôt pousser un élément intégré plus loin. Il est généralement préférable d'utiliser un élément natif autant que possible (CSS, quelques écouteurs, un pseudo-élément occasionnel) plutôt qu'un élément personnalisé, car vous conservez l'accessibilité et le comportement de la plate-forme sans frais. Passez à un élément personnalisé lorsque les solutions de contournement ne sont plus gérables, c'est-à-dire lorsque vous devez lutter contre le comportement intégré de l'élément ou que l'expérience de création devient pire que celle d'une API de composant propre. Le fait d'avoir besoin d'un peu de JS sur un élément intégré n'est pas, en soi, une raison de le remplacer.
Règles transversales
Elles s'appliquent à tous les guides ci-dessous :
- OBLIGATOIRE : le nom de la balise doit être au format kebab-case avec au moins un trait d'union (par exemple,
my-element). C'est ce qui distingue les éléments personnalisés des éléments intégrés actuels et futurs. - OBLIGATOIRE : appelez d'abord
super()dans le constructeur, avant de toucher àthis. Si vous l'appelez après une instruction qui lit ou écritthis, une exception est générée. - OBLIGATOIRE : utilisez des éléments sémantiques (
<button>,<nav>,<ul>) dans les modèles de shadow DOM. La plupart des problèmes d'accessibilité dans les arbres fantômes proviennent d'un balisage non sémantique. Un emplacement ne modifie pas le rôle de ce qu'il projette. - OBLIGATOIRE : Les références ARIA
id(aria-labelledby,aria-describedby,aria-controls) ne peuvent pas franchir une limite de shadow DOM. L'élément de référence et l'idvers lequel il pointe doivent se trouver dans la même arborescence. - Privilégiez
attachShadow({ mode: 'open' })pour que les outils de développement, les scripts de page et l'hydratation du serveur puissent atteindreelement.shadowRoot. N'utilisez'closed'que lorsque vous devez masquer la structure interne des scripts de page. - Évitez les éléments intégrés personnalisés (
extends HTMLButtonElementavecis="…"), car Safari a définitivement refusé de les proposer.
Matrice des guides de cas d'utilisation
Identifiez la tâche et récupérez son guide :
| Si vous devez… | Récupérer |
|---|---|
| Définir une nouvelle balise sémantique avec un comportement et un cycle de vie | Éléments personnalisés |
| Limiter la portée des styles et du balisage à un composant, et y projeter le contenu du consommateur | Shadow DOM, modèles et emplacements |
| Afficher l'arborescence shadow d'un composant sur le serveur (SSR) sans flash de contenu non stylisé | Prérendu des éléments personnalisés |
Appliquer un style au-delà de la limite du shadow DOM (thèmes, ::part, propriétés personnalisées) |
Styliser des composants Web |
Envoyer la valeur d'un contrôle personnalisé à un <form> environnant |
Éléments personnalisés associés à un formulaire |
| Rendre un élément personnalisé accessible (rôles, focus, ARIA sur les racines fantômes) | Composants Web accessibles |