Aller au contenu principal
Site en cours de refonte — quelques pages peuvent bouger ou évoluer.
30 mars 2026

WordPress ajoute :hover/:focus dans theme.json : enfin un focus ring cohérent (sans bagarre CSS)

Tu veux un focus ring propre sur tout le site, sans te battre avec 15 styles inline et des blocs qui écrasent tout ? WordPress bouge dans le bon sens… mais il faut savoir où ça mord.

11 min de lecture
45 vues
réactions
Partager :
WordPress ajoute :hover/:focus dans theme.json : enfin un focus ring cohérent (sans bagarre CSS)

Si tu as déjà tenté d’avoir un focus ring cohérent sur un site WordPress un peu vivant (Gutenberg, quelques patterns, deux plugins qui ajoutent leurs blocs), tu connais le sport. Un bloc met un style inline, un autre arrive avec une classe bizarre, ton thème surcharge à moitié, et au final tu te retrouves à empiler du CSS « plus spécifique » pour reprendre la main. Mauvaise nouvelle : ça finit toujours en guerre de spécificité. Bonne nouvelle : theme.json commence enfin à comprendre les pseudo-états (:hover, :focus, :focus-visible, :active). Et ça change vraiment la donne pour standardiser l’UI.

Pseudo-états dans theme.json : ce que WordPress débloque (vraiment)

Le cœur du sujet, c’est que WordPress pousse de plus en plus le styling côté Style Engine et Global Styles. L’idée n’est plus « ton thème balance une feuille CSS monolithique et on prie », mais « on décrit un système de styles, et WordPress génère le CSS qui va bien ».

Jusqu’ici, c’était bien pour les styles “au repos” (typo, couleurs, espacements, boutons…), mais dès que tu voulais gérer des comportements d’interface, tu retombais vite dans le CSS à la main. En particulier sur les deux états qui font le plus mal : hover (cohérence visuelle) et focus (accessibilité clavier). Le support des pseudo-états dans theme.json vient combler ce trou.

Concrètement, ça te permet de définir des styles d’état au même endroit que le reste de ton design system WordPress. Et rien que ça, dans une base où tu as des éditeurs qui bidouillent des blocs, c’est un gain énorme en cohérence et en maintenance.

Pourquoi ça peut calmer la guerre de spécificité (et quand ça ne suffit pas)

Quand tu styles via theme.json, WordPress génère du CSS “global” qui vise les bons sélecteurs Gutenberg et les bons wrappers. En général, tu te retrouves avec une couche cohérente, appliquée partout, sans devoir viser toi-même tous les cas possibles.

Mais attention, ça ne veut pas dire que la spécificité disparaît par magie. Il reste au moins trois sources de conflits que je vois en vrai :

D’abord, les styles de blocs et leurs variations. Certains blocs, ou certains patterns, embarquent des styles qui prennent le dessus, parfois via des règles plus spécifiques, parfois via un ordre de chargement qui ne t’arrange pas.

Ensuite, les styles inline injectés par l’éditeur quand quelqu’un change un réglage dans le panneau (couleur, bordure, etc.). Un inline style, ça gagne souvent contre ta règle globale, même si ton design system était nickel.

Enfin, il y a l’écosystème : plugins, bibliothèques CSS, thèmes enfants, “petites” feuilles ajoutées en urgence. Si tu as un plugin qui colle un outline: none ou un hover agressif sur ses composants, theme.json ne te sauvera pas si tu laisses passer ça.

La bonne lecture, c’est : theme.json te donne un point de vérité pour tes états, et ça réduit drastiquement le besoin de surcharger à la main. Mais tu dois quand même gérer les collisions, et surtout éviter de réintroduire toi-même le chaos.

Configurer :hover, :focus et :focus-visible dans theme.json (exemple concret)

La mécanique exacte dépend de la version WordPress/Gutenberg que tu cibles, mais l’idée est la même : déclarer des styles d’état au niveau global et/ou au niveau d’un élément (bouton, liens…), et laisser WordPress générer les sélecteurs.

Voici un exemple réaliste pour des boutons et des liens. L’objectif n’est pas de faire “joli sur une capture”, mais d’avoir un comportement robuste : hover lisible, focus ring visible au clavier, et pas de focus ring qui clignote à la souris si tu relies sur :focus-visible.

{
  "version": 3,
  "settings": {
    "color": {
      "palette": [
        { "slug": "brand", "color": "#2563eb", "name": "Brand" },
        { "slug": "brand-strong", "color": "#1d4ed8", "name": "Brand strong" },
        { "slug": "text", "color": "#0f172a", "name": "Text" },
        { "slug": "surface", "color": "#ffffff", "name": "Surface" }
      ]
    }
  },
  "styles": {
    "elements": {
      "link": {
        "color": { "text": "var(--wp--preset--color--brand)" },
        ":hover": {
          "color": { "text": "var(--wp--preset--color--brand-strong)" }
        },
        ":focus-visible": {
          "outline": "3px solid var(--wp--preset--color--brand)",
          "outlineOffset": "3px"
        }
      },
      "button": {
        "color": {
          "text": "var(--wp--preset--color--surface)",
          "background": "var(--wp--preset--color--brand)"
        },
        ":hover": {
          "color": {
            "background": "var(--wp--preset--color--brand-strong)"
          }
        },
        ":focus-visible": {
          "outline": "3px solid var(--wp--preset--color--text)",
          "outlineOffset": "3px"
        },
        ":active": {
          "transform": "translateY(1px)"
        }
      }
    }
  }
}

Deux remarques terrain.

La première : ne joue pas au designer avec :focus. Si tu enlèves l’outline natif sans le remplacer correctement, tu casses l’accessibilité clavier, point. Et sur WordPress, ça veut souvent dire des menus, des boutons, des formulaires, des modales. Le focus est partout. Fais simple, fais visible, et vérifie le contraste.

La deuxième : :focus-visible est ton ami, mais il faut accepter sa logique. Au clavier, tu verras ton ring. À la souris, souvent non. C’est exactement ce qu’on veut pour éviter une UI qui “flash” au clic. Si tu as des composants qui doivent afficher un état au clic souris (par exemple une navigation), c’est un autre sujet : tu gères alors un état “current/selected” via classes ou attributs ARIA, pas via focus.

Accessibilité : le focus ring, ce n’est pas un détail cosmétique

La raison pour laquelle je suis content de voir ce support arriver dans theme.json, c’est que ça encourage une approche “système” plutôt qu’un patch par page. Un focus ring uniforme, c’est un vrai confort de navigation au clavier, et c’est aussi un signal de qualité sur un site business.

Ce que je recommande : un ring assez épais (2–3px), avec un outline-offset pour ne pas manger le composant, et une couleur qui ressort sur la majorité des backgrounds. Si ton site a des sections sombres et claires, tu vas vite tomber sur un point intéressant : un focus ring unique ne marche pas partout. Dans ce cas, tu as deux options propres : soit tu choisis une couleur “universelle” (souvent une teinte saturée qui contraste sur presque tout), soit tu définis des variantes par contexte (par exemple via des styles de blocs/sections qui adaptent les variables CSS).

Et non, je ne conseille pas de “cacher le focus ring sauf pour l’accessibilité”. L’accessibilité, ce n’est pas un mode secret. Tu peux faire un focus ring beau, net, et cohérent avec la DA. Ce n’est pas l’un ou l’autre.

Les pièges réels : blocs qui écrasent, plugins bruyants, et styles inline

Le premier piège, c’est le bloc qui a ses propres règles d’état. Typiquement, certains boutons générés via des blocs tiers (ou des patterns importés) arrivent avec des sélecteurs du type .wp-block-xxx .button:hover et parfois des !important. Dans ce cas, theme.json ne “perd” pas parce qu’il est nul, il perd parce que quelqu’un a décidé de tricher.

Le deuxième piège, plus sournois : l’éditeur. Un contributeur change la couleur d’un bouton au cas par cas, et WordPress injecte un style inline ou une classe utilitaire spécifique à ce bloc. Résultat : ton hover global devient incohérent, ou ton focus ring se voit moins. C’est là que ta stratégie de design system doit être claire. Soit tu assumes la liberté et tu acceptes une UI “un peu variée”. Soit tu verrouilles certains réglages et tu fournis des styles de composants réutilisables plutôt que du réglage à la carte.

Le troisième piège, c’est le plugin “UX” ou “builder” qui veut être sympa, et qui ajoute des resets agressifs. Le classique, c’est outline: none; sur tout ce qui bouge. Ça ne se voit pas à la souris, donc ça passe en recette, et ensuite tu te retrouves avec un site inutilisable au clavier. Si tu dois faire une seule chose côté QA, c’est un tour du site au clavier sur les pages qui comptent.

Une méthode simple qui marche : un set de blocs “référence” + tests clavier

Quand j’attaque ce sujet sur un site WordPress existant, je ne commence pas par “réécrire le CSS”. Je commence par fabriquer une petite page de référence, un peu comme une storybook pauvre, mais dans WordPress. Une page avec les blocs que le site utilise vraiment : boutons, liens, navigation, formulaires, accordéons, call-to-action, et deux-trois mises en page. Pas besoin d’en faire 50. Il faut juste couvrir les composants qui te posent des problèmes de hover/focus.

Ensuite, je pose une règle : theme.json est la source de vérité pour les états globaux. Je corrige d’abord ce qui peut l’être dans theme.json (couleurs, outlines, offsets). Puis seulement après, je traite les exceptions. Les exceptions, c’est du CSS ciblé, assumé, documenté, et idéalement limité à un plugin/bloc précis. Le but n’est pas de revenir à la guerre de spécificité, mais de gérer les points de friction sans contaminer le reste.

Enfin, je teste au clavier. Pas un “petit test rapide”. Un vrai tour : tabulation, entrée, espace, focus dans la navigation, focus dans les formulaires, et vérification que rien ne se retrouve sans indicateur visuel. Si tu as des modales, c’est encore plus important, parce qu’un focus mal géré dans une modale, c’est un piège total pour l’utilisateur clavier.

Fallback et compat : ce que je garde sous le coude

Ce support des pseudo-états arrive dans un contexte WordPress qui bouge vite. Selon ton parc, tu vas avoir des sites sur différentes versions, parfois sans le dernier Gutenberg. Si tu dois maintenir une compat large, tu peux prévoir un fallback CSS minimal, juste pour garantir un focus visible partout, même si theme.json ne génère pas encore exactement ce que tu attends.

/* Fallback minimal : si un plugin/ancien thème casse le focus, tu restes safe. */
:where(a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])):focus-visible {
  outline: 3px solid #2563eb;
  outline-offset: 3px;
}

Oui, ça ressemble à “je remets du CSS”. Mais c’est un filet de sécurité, pas un système parallèle. Et c’est franchement préférable à un site où tu perds l’indicateur de focus parce qu’un plugin a décidé que c’était plus “clean”.

Ce que ça change pour la maintenabilité (et pourquoi tu devrais t’y intéresser)

Si tu fais du WordPress “business”, tu passes ta vie à arbitrer : livrer vite, garder une UI propre, et ne pas transformer chaque tweak en régression. Centraliser les états hover/focus dans theme.json, c’est exactement le genre de petit changement qui évite les dégâts sur la durée.

Tu réduis les CSS ad hoc collés à la va-vite. Tu limites les overrides en cascade. Tu rends les audits plus simples, parce que tu sais où regarder. Et surtout, tu peux dire à ton client ou ton équipe : « le focus ring, c’est standard, c’est géré au niveau thème, et ça ne dépend pas d’un bloc choisi au hasard ».

La suite logique, si tu veux pousser le truc proprement, c’est de traiter tes blocs comme des composants : quelques variantes bien nommées, des règles d’état cohérentes, et moins de réglages “à la main” dans l’éditeur. WordPress n’est pas (encore) un framework design system parfait. Mais là, on va clairement dans la bonne direction.

Sources

Cet article vous a plu ?

Commentaires

Laisser un commentaire

Entre 10 et 2000 caractères

Les commentaires sont modérés avant publication.

Aucun commentaire pour le moment.

Soyez le premier à donner votre avis !