Aller au contenu principal
HTML

Invoker Commands : piloter l’UI sans JavaScript maison

Les Invoker Commands relient un bouton à une action native : popover, dialogue ou plein écran. Cas d’usage, limites et repli progressif.

Par Julien Martel 7 min de lecture
Invoker Commands : piloter l’UI sans JavaScript maison

Un bouton qui ouvre une boîte de dialogue, affiche un panneau de filtres ou ferme une interface modale paraît banal. Pourtant, ces gestes mobilisent encore souvent quelques lignes de JavaScript, des attributs ARIA mis à jour manuellement, des écouteurs d’événements et une logique de nettoyage qui se répète écran après écran.

Les Invoker Commands proposent une voie plus directe pour une partie de ces interactions : un bouton décrit l’action qu’il souhaite déclencher et l’élément HTML qu’il vise. Le navigateur exécute alors une commande native, par exemple ouvrir un dialog, afficher un popover ou demander le plein écran.

Ce n’est ni un nouveau framework ni une raison de supprimer JavaScript par principe. C’est une évolution de HTML à utiliser là où elle est pertinente : pour les comportements déjà définis par la plateforme. Sur un site produit, le gain vient moins de la quantité de code supprimée que de la réduction des petits scripts d’interface difficiles à homogénéiser et à maintenir.

Le problème concret : des interactions natives encore recodées à la main

Dans de nombreux projets, l’ouverture d’une modale ressemble à ceci : un clic sur un bouton, la recherche d’un élément par son identifiant, un appel à showModal(), puis un autre écouteur pour fermer la fenêtre. Pour un popover, on ajoute souvent une classe CSS, un état JavaScript, une gestion du clic extérieur et parfois une synchronisation de aria-expanded.

Une partie de cette logique est justifiée lorsqu’elle sert le produit. Mais elle ne l’est pas toujours. Si l’objectif est simplement d’ouvrir une confirmation de suppression ou un petit menu contextuel, le navigateur connaît déjà les primitives concernées :

  • dialog pour les fenêtres de dialogue, notamment modales ;
  • Popover API pour les surfaces temporaires comme des panneaux, aides ou menus ;
  • Fullscreen API pour demander l’affichage plein écran d’un élément.

Les Invoker Commands apportent une convention déclarative au-dessus de ces primitives. Le bouton devient l’initiateur de l’action, et l’élément cible reste responsable de son propre comportement natif.

L’intérêt est particulièrement net dans une base de code où les mêmes micro-interactions se répètent. Au lieu d’avoir plusieurs variantes d’un utilitaire openModal, chaque composant peut exprimer son intention directement dans son HTML. Cette approche rejoint l’idée développée dans notre analyse du JavaScript optionnel : retirer du code n’a de valeur que si le navigateur prend réellement le relais avec un comportement adapté.

Le modèle : command indique l’action, commandfor indique la cible

Les Invoker Commands reposent principalement sur deux attributs placés sur un bouton :

  • command contient le nom de la commande à envoyer ;
  • commandfor contient l’identifiant de l’élément ciblé.

Le mécanisme est intentionnellement simple. Le bouton doit pointer vers un identifiant unique dans le document. Lors de l’activation par clic, clavier ou autre moyen pris en charge par le navigateur, la commande est transmise à l’élément visé.

Voici un exemple de dialogue de confirmation :

<button
  type="button"
  command="show-modal"
  commandfor="delete-dialog">
  Supprimer le projet
</button>

<dialog id="delete-dialog">
  <h2>Supprimer ce projet ?</h2>
  <p>Cette action est irréversible.</p>

  <form method="dialog">
    <button value="cancel">Annuler</button>
    <button value="confirm">Supprimer</button>
  </form>
</dialog>

Dans un navigateur qui prend cette commande en charge, l’activation du premier bouton demande au dialogue de s’ouvrir de manière modale. Le formulaire interne avec method="dialog" constitue quant à lui un mécanisme natif utile pour fermer la fenêtre et récupérer le choix de l’utilisateur via l’événement close.

La séparation est saine : HTML s’occupe de l’ouverture et de la fermeture de l’interface ; JavaScript, s’il est nécessaire, traite l’action métier après confirmation. Par exemple, un appel fetch() pour supprimer un projet ne disparaît pas. Il ne devrait pas disparaître : c’est une règle applicative, pas un détail de présentation.

Pour connaître les valeurs prévues et l’état précis de l’API, consultez la documentation de référence sur la propriété command de HTMLButtonElement sur MDN. Cette vérification est importante : les fonctionnalités HTML récentes évoluent plus vite que les habitudes de développement.

Popovers et dialogues : les commandes les plus utiles au quotidien

Les commandes intégrées couvrent des opérations qui correspondent aux méthodes natives des éléments ciblés. Pour les popovers, les valeurs les plus parlantes sont show-popover, hide-popover et toggle-popover. Pour un dialogue, on rencontre notamment show-modal, close et request-close.

Un panneau d’informations temporaire peut ainsi être exprimé de cette façon :

<button
  type="button"
  command="toggle-popover"
  commandfor="delivery-help">
  Voir les délais de livraison
</button>

<div id="delivery-help" popover>
  <p>Les délais dépendent de l’adresse et du mode de livraison choisi.</p>
</div>

Le rôle de popover n’est pas de remplacer toutes les fenêtres flottantes. Un popover convient bien à un contenu bref, contextuel et non bloquant : un complément d’information, un panneau de filtres compact ou des actions secondaires. Une confirmation destructive ou une étape qui exige une décision explicite est généralement plus cohérente dans un dialog modal.

Pour le seul besoin d’ouvrir ou fermer un popover, il existe aussi popovertarget et popovertargetaction. Ces attributs sont souvent le choix le plus simple lorsque l’on vise exclusivement la Popover API :

<button
  type="button"
  popovertarget="account-menu"
  popovertargetaction="toggle">
  Mon compte
</button>

<nav id="account-menu" popover aria-label="Menu du compte">
  <a href="/compte/">Mon profil</a>
  <a href="/deconnexion/">Se déconnecter</a>
</nav>

Autrement dit, n’introduisez pas command partout par réflexe. Si popovertarget répond exactement au besoin avec une compatibilité plus adaptée à votre public, il reste une excellente solution. Les Invoker Commands deviennent plus intéressantes lorsqu’un même modèle de bouton doit cibler plusieurs actions natives, y compris des dialogues.

Cette approche complète les principes évoqués dans notre guide sur les formulaires avec moins de JavaScript : commencer par les capacités du navigateur réduit souvent le code d’orchestration, sans réduire les exigences métier.

request-close : laisser l’application refuser une fermeture

La différence entre close et request-close mérite une attention particulière. Une fermeture directe exécute la fermeture du dialogue. Une demande de fermeture, elle, permet au dialogue de déclencher son cycle de fermeture annulable.

C’est utile lorsqu’un dialogue contient un formulaire modifié mais non enregistré. L’application peut écouter l’événement cancel, vérifier si des changements sont présents, puis empêcher la fermeture et demander confirmation. La commande reste déclarative côté bouton, tandis que la logique de protection des données reste là où elle doit être : dans le code applicatif.

<button
  type="button"
  command="request-close"
  commandfor="profile-dialog">
  Fermer
</button>

Ce détail évite une mauvaise simplification : remplacer toute logique de fermeture par une commande native alors que le produit a des règles spécifiques. Les primitives HTML offrent un cycle d’interaction fiable ; elles ne connaissent pas l’état de votre formulaire, vos droits métier ou la présence d’une requête en cours.

Le même principe s’applique au plein écran avec une commande telle que request-fullscreen. Le navigateur conserve ses règles de sécurité : la demande doit notamment résulter d’une activation utilisateur et peut être limitée par la politique de permissions du contexte. Le plein écran n’est donc pas un état que l’on peut imposer silencieusement depuis n’importe quel script ou bouton.

Accessibilité : le natif aide, mais ne rédige pas l’interface à votre place

Utiliser un véritable élément button apporte d’emblée une interaction clavier et une sémantique de contrôle. Utiliser dialog pour une modale évite aussi de reconstruire entièrement le comportement modal avec des div. C’est une base solide, mais pas une garantie automatique d’accessibilité.

Quelques règles restent nécessaires :

  • donner au bouton un libellé explicite, par exemple « Ouvrir les filtres » plutôt qu’une icône non étiquetée ;
  • structurer le contenu du dialogue avec un titre et une explication claire de l’action attendue ;
  • prévoir une action d’annulation visible pour les décisions importantes ;
  • ne pas employer un popover comme une modale si l’utilisateur doit impérativement traiter son contenu ;
  • tester la navigation au clavier, la fermeture avec Échap lorsqu’elle est attendue et le rendu avec un lecteur d’écran.

Le HTML natif est préférable à une imitation, mais le contenu reste votre responsabilité. Un dialogue ouvert correctement qui contient deux boutons tous deux libellés « OK » demeure une mauvaise expérience. De même, un menu de navigation doit conserver des liens réels : une commande peut ouvrir sa surface, elle ne transforme pas des contrôles vagues en navigation accessible.

Les tests manuels doivent être complétés par des outils comme axe, Lighthouse dans Chrome DevTools et, selon votre environnement, un lecteur d’écran. Ils ne remplacent pas une vérification humaine du parcours, mais ils détectent des erreurs structurelles récurrentes.

Compatibilité : prévoir un repli avant de supprimer le script existant

Les Invoker Commands sont une évolution récente de la plateforme. Leur prise en charge ne doit pas être supposée à partir du seul fait que dialog ou les popovers fonctionnent dans un navigateur. Les APIs sous-jacentes et l’attribut command n’ont pas nécessairement le même niveau de disponibilité selon les moteurs et les versions.

Avant une mise en production, consultez le tableau de compatibilité de la documentation MDN, puis testez les navigateurs réellement utilisés par votre audience. Un navigateur de test sur votre machine ne représente ni un parc d’entreprise, ni un appareil ancien, ni une webview intégrée à une application.

La stratégie la plus durable consiste à traiter les commandes comme une amélioration progressive :

  • conserver un contenu utilisable sans que l’interface flottante soit indispensable ;
  • utiliser les attributs plus ciblés et établis, comme popovertarget, lorsque le cas d’usage s’y prête ;
  • ajouter un petit repli JavaScript uniquement lorsque command n’est pas disponible ;
  • ne pas recréer à la main un faux dialogue modal complet si un niveau de compatibilité ancien est une contrainte forte.

Pour un dialogue, un repli conditionnel peut rester très court. L’exemple suivant ne s’exécute que si l’interface des commandes n’est pas exposée par le navigateur :

<button
  type="button"
  command="show-modal"
  commandfor="settings-dialog"
  data-dialog-fallback="settings-dialog">
  Ouvrir les réglages
</button>

<dialog id="settings-dialog">
  <p>Réglages du compte</p>
</dialog>

<script>
  if (!("command" in HTMLButtonElement.prototype)) {
    document.querySelectorAll("[data-dialog-fallback]").forEach((button) => {
      button.addEventListener("click", () => {
        const dialog = document.getElementById(button.dataset.dialogFallback);

        if (dialog && typeof dialog.showModal === "function") {
          dialog.showModal();
        }
      });
    });
  }
</script>

Dans un projet réel, ce repli doit aussi couvrir la fermeture et être testé avec vos contraintes d’accessibilité. Le point essentiel est de ne pas livrer deux systèmes concurrents qui s’exécutent simultanément. La détection de capacité limite le JavaScript aux environnements qui en ont besoin.

Les commandes personnalisées existent, mais elles ne remplacent pas votre logique métier

Le modèle prévoit également des commandes personnalisées, généralement nommées avec un préfixe --. Elles permettent de transmettre une intention à une cible qui écoutera un événement de commande. Cela peut être intéressant dans un composant web ou une bibliothèque d’interface, notamment pour standardiser le couplage entre un bouton et un composant.

Mais une commande personnalisée nécessite toujours du JavaScript pour interpréter l’événement. Elle ne supprime donc pas la logique applicative ; elle change surtout sa forme. Dans une application modeste, un gestionnaire d’événement explicite est souvent plus lisible qu’une couche d’abstraction supplémentaire.

Réservez ce mécanisme aux cas où vous avez une convention de composants claire, plusieurs invocations comparables et une vraie valeur de standardisation. Pour une action isolée, un écouteur local bien nommé peut être plus simple à comprendre lors d’une revue de code.

Quand JavaScript reste le meilleur choix

Les Invoker Commands ne sont pas adaptées à toutes les interfaces. JavaScript reste préférable lorsque l’interaction dépend d’un état métier, de données asynchrones ou d’une logique de positionnement complexe.

  • ouvrir un panneau uniquement après la réponse d’une API ;
  • bloquer une action selon les permissions récupérées pour l’utilisateur connecté ;
  • gérer une séquence d’étapes, un assistant de commande ou un workflow de validation ;
  • animer une interface selon un état applicatif précis ;
  • positionner un élément flottant selon des règles avancées liées à la géométrie de la page ;
  • maintenir un état partagé entre plusieurs composants éloignés.

Dans ces situations, la commande native peut parfois rester une brique utile, mais elle ne doit pas masquer la véritable source de vérité. Un état de panier, de session ou de formulaire n’appartient pas au DOM seul. Il doit être géré explicitement dans l’architecture de l’application.

Cette frontière est proche de celle que l’on retrouve avec les Web Components utilisés avec pragmatisme : encapsuler une interaction utile est bénéfique, mais encapsuler une complexité inexistante ne l’est pas.

Conclusion : adopter les Invoker Commands là où elles retirent une vraie répétition

Les Invoker Commands ne changent pas la nature du développement web. Elles offrent surtout une manière plus déclarative de relier des boutons aux comportements natifs de dialog, des popovers et du plein écran. Pour les actions d’interface simples, cela peut retirer du JavaScript répétitif tout en laissant au navigateur le soin de gérer ses primitives.

Le bon réflexe n’est pas de réécrire toutes les modales existantes. Commencez plutôt par un écran secondaire, vérifiez la compatibilité avec votre audience, testez le clavier et les lecteurs d’écran, puis comparez la simplicité obtenue avec votre solution actuelle. Si le HTML rend le composant plus lisible sans compromettre le repli, il mérite sa place dans votre prochaine itération.