12k
All articles

Gérer le focus et l'interactivité avec l'attribut Inert

Utilisez lattribut inert pour isoler modales, tiroirs et overlays de chargement, en bloquant focus, clics et accès à larbre daccessibilité.

OpenReplay Team
OpenReplay Team
Gérer le focus et l'interactivité avec l'attribut Inert

Appliquer inert à un élément retire l’intégralité de son sous-arbre de l’ordre de tabulation, bloque tous les événements de pointeur et de clic, et le masque de l’arbre d’accessibilité afin que les lecteurs d’écran ne puissent ni le découvrir ni l’annoncer — ces trois comportements sont imposés par la spécification WHATWG HTML Living Standard. De plus, les navigateurs actuels empêchent la recherche dans la page (Ctrl/Cmd+F) de correspondre au texte du sous-arbre et désactivent la sélection de texte en son sein ; des comportements que la spécification laisse à la discrétion des agents utilisateur, mais que MDN documente comme la norme implémentée. Ce sont donc six canaux d’interaction désactivés avec un seul attribut.

Cet article répond à un problème précis : comment désactiver proprement le contenu en arrière-plan lorsqu’une modale, un tiroir ou une navigation latérale s’ouvre — sans avoir à coder manuellement un piège de focus qui casse sur les lecteurs d’écran mobiles, ni à disperser des aria-hidden dans tout le DOM. Il couvre ce que inert bloque, la syntaxe HTML et JavaScript pour l’appliquer, une modale complète et fonctionnelle avec restauration du focus, comment styliser le contenu inerte, et quand utiliser disabled, aria-hidden ou hidden à la place.

Points clés à retenir

  • inert bloque six canaux d’interaction en une seule déclaration : le focus, les événements de pointeur et de clic, l’ordre de tabulation et la découvrabilité dans l’arbre d’accessibilité (tous imposés par la spécification), ainsi que la recherche dans la page et la sélection de texte (à la discrétion de l’agent utilisateur, mais implémentés dans les navigateurs actuels).
  • inert est devenu Baseline Newly available en avril 2023 — Chrome et Edge 102, Firefox 112, Safari 15.5 — et a atteint le statut Baseline Widely available aux alentours d’octobre 2025, faisant du polyfill wicg-inert un élément de contexte historique plutôt qu’une exigence de production.
  • Le changement de paradigme est celui de la garde versus du piège : un piège de focus enferme les utilisateurs dans un composant via JavaScript ; inert protège le reste de la page afin que le navigateur impose la frontière nativement.
  • Au-delà de l’attribut booléen HTML, inert est exposé comme propriété IDL HTMLElement.inert, un booléen que l’on définit en JavaScript — mainEl.inert = true à l’ouverture, mainEl.inert = false à la fermeture.
  • <dialog>.showModal() rend automatiquement le reste de la page inerte, de sorte que la gestion manuelle de inert n’est nécessaire que pour les modales personnalisées construites en dehors de l’élément natif.

Ce que l’attribut inert bloque

inert est un attribut HTML global qui rend un élément et l’intégralité de son sous-arbre non interactifs et non découvrables. Conformément à la section sur les sous-arbres inertes du WHATWG HTML Living Standard, un nœud inerte ne reçoit pas d’événements d’interaction utilisateur ciblés tels que click et focus, et les agents utilisateur doivent l’exclure de l’arbre d’accessibilité. Trois blocages sont normatifs et cohérents dans toutes les implémentations :

  1. Focus — les éléments inertes ne peuvent pas recevoir le focus via Tab, un clic ou element.focus() par programmation.
  2. Événements de pointeur et de clic — les clics et événements de pointeur initiés par l’utilisateur n’atteignent pas les nœuds inertes.
  3. Découvrabilité dans l’arbre d’accessibilité — les technologies d’assistance ne peuvent ni trouver ni annoncer le sous-arbre.

Deux blocages supplémentaires sont laissés à la discrétion de l’agent utilisateur dans la spécification (qui emploie le terme normatif may), mais MDN les documente comme le comportement implémenté dans les navigateurs actuels :

  1. Recherche dans la page — Ctrl/Cmd+F ne trouve pas de correspondance dans le texte d’un sous-arbre inerte.
  2. Sélection de texte — les utilisateurs ne peuvent pas sélectionner le texte inerte.

L’ordre de tabulation découle du blocage du focus : les nœuds inertes étant impossibles à focaliser, ils sont entièrement retirés de la navigation séquentielle par focus. Une limite importante : inert bloque les événements initiés par l’utilisateur, pas les événements programmatiques. Un appel à dispatchEvent() ou un timer se déclenchant à l’intérieur d’un sous-arbre inerte s’exécute toujours — inert n’est pas alert() et ne gèle pas l’exécution JavaScript.

L’écueil à bien intégrer : comme inert retire le sous-arbre de l’arbre d’accessibilité, ne l’appliquez jamais à un contenu qu’un utilisateur a encore besoin de lire. Si vous avez uniquement besoin de masquer quelque chose visuellement tout en le maintenant découvrable, il faut utiliser un autre outil.

Les deux cas d’usage canoniques

inert existe pour deux situations, toutes deux documentées dans le guide inert de web.dev : le DOM présent mais hors écran ou masqué, et le DOM visible mais qui ne doit pas être interactif.

DOM hors écran ou masqué. Un tiroir de navigation coulissant ou une navigation latérale ajoute des liens focalisables au DOM avant qu’ils ne soient visibles. Sans inert, les utilisateurs au clavier peuvent naviguer par Tab dans le tiroir fermé et atterrir sur des contrôles qu’ils ne peuvent pas voir. Marquer le conteneur du tiroir comme inerte jusqu’à son ouverture maintient ces liens hors de l’ordre de tabulation :

<nav id="drawer" inert>
  <a href="/dashboard">Dashboard</a>
  <a href="/settings">Settings</a>
</nav>

Interface utilisateur visible mais non interactive. Lorsqu’un formulaire est en cours de soumission, qu’une page est en cours de chargement, ou qu’une superposition modale recouvre le contenu en arrière-plan, ce contenu est visuellement présent mais ne doit pas accepter de saisie. Appliquer inert au formulaire pendant la soumission évite les doubles soumissions et les focus parasites :

<form id="signup" inert>
  <!-- champs désactivés en groupe pendant que la requête est en cours -->
</form>

Les deux cas partagent la même logique : le contenu reste dans le DOM (de sorte que la mise en page, les transitions et l’état sont préservés), mais le navigateur refuse d’y router les interactions.

Syntaxe : l’attribut HTML et la propriété HTMLElement.inert

inert dispose de deux interfaces : l’attribut booléen HTML et la propriété IDL HTMLElement.inert. L’attribut est destiné au balisage statique ou rendu côté serveur ; la propriété est utilisée pour basculer l’état en JavaScript.

En tant qu’attribut booléen, c’est sa présence qui compte — inert et inert="" sont équivalents, et la valeur par défaut est false (absent signifie interactif) :

<main inert>
  <!-- tout ce qui se trouve ici est non interactif -->
</main>

Pour le basculer à l’exécution, utilisez la propriété HTMLElement.inert, un booléen que vous pouvez lire et définir directement — sans avoir recours à setAttribute / removeAttribute :

const mainEl = document.querySelector('main');

// désactiver l'interaction avec le reste de la page
mainEl.inert = true;

// la restaurer
mainEl.inert = false;

C’est la partie la plus élégante de l’API, et celle qui manque dans la plupart des articles existants : le basculement ouverture/fermeture se résume à deux affectations. Comparez cela à la procédure en huit étapes d’un piège de focus décrite ci-dessous.

Avant inert : les pièges de focus et leur fragilité

Avant inert, la méthode standard pour délimiter une modale était un piège de focus JavaScript — une logique qui intercepte Tab et Shift+Tab pour boucler le focus à l’intérieur de la boîte de dialogue. La procédure canonique, détaillée par CSS-Tricks, comporte environ huit étapes : trouver chaque élément focalisable de la page, identifier le premier et le dernier élément focalisable à l’intérieur de la modale, retirer l’interactivité et la découvrabilité de tout ce qui se trouve en dehors, déplacer le focus à l’intérieur, écouter les événements de fermeture, tout restaurer à la fermeture, et renvoyer le focus à l’élément déclencheur.

La première étape — « trouver chaque élément focalisable » — est elle-même source de bugs, car l’ensemble des éléments nativement focalisables est plus large que ce dont la plupart des développeurs se souviennent. Les éléments qui reçoivent le focus dans l’ordre de tabulation séquentiel sans aucun tabindex sont :

  • <a> et <area> avec un attribut href
  • <button>, <input>, <select> et <textarea> (sauf si disabled)
  • <iframe>, <embed> et <object>
  • <audio> et <video> avec un attribut controls
  • <summary> (le premier à l’intérieur d’un <details>)
  • tout élément avec un tabindex non négatif, et tout élément contenteditable

En oublier un lors de la construction d’un piège permet à un utilisateur au clavier d’en sortir ; en gérer trop revient à se battre contre l’ordre natif du navigateur. La valeur par défaut la plus sûre est de laisser l’ordre de focus naturel du document intact et d’intervenir uniquement lorsqu’un composant le requiert véritablement — ce qui représente l’essentiel du travail manuel qu’inert supprime.

Le changement de paradigme est l’essentiel du propos. Un piège de focus enferme les utilisateurs dans un composant en interceptant les frappes clavier ; inert protège le reste de la page en rendant tout ce qui se trouve en dehors de la boîte de dialogue inaccessible — c’est le navigateur qui impose la frontière, et non votre JavaScript. Cette opposition garde/piège est formulée dans le traitement de l’attribut par LogRocket.

Les pièges codés manuellement échouent de trois manières récurrentes :

  • Technologies d’assistance mobiles. TalkBack sur Android et VoiceOver sur iOS naviguent par gestes de balayage, et non par frappes sur Tab. Un piège JavaScript qui n’intercepte que les événements clavier ne fournit aucune frontière pour les utilisateurs de lecteurs d’écran naviguant par balayage. inert bloque le sous-arbre au niveau de la plateforme, couvrant à la fois la navigation au clavier et la navigation par gestes.
  • Prolifération de aria-hidden. La solution de contournement avant inert consistait à définir aria-hidden="true" sur chaque élément hors modale. Sur une page avec des arbres DOM profonds, cela devient ingérable et souvent incomplet.
  • Boucles de tabulation manuelles. La logique d’interception Tab/Shift+Tab est fragile et facile à mal implémenter, surtout lorsque le contenu focalisable de la modale change.

Les enregistrements de sessions sur des implémentations de modales et de tiroirs font fréquemment apparaître des événements de focus atterrissant sur le contenu en arrière-plan alors qu’une boîte de dialogue est encore ouverte — la signature d’une frontière de focus incomplète, et précisément ce qu’inert est conçu pour éliminer.

Les références citées plus haut reconstruisent un trap-focus.js complet ; il n’est pas nécessaire de le reproduire ici. La comparaison pertinente est le nombre de lignes. Le piège représente des dizaines de lignes d’interception d’événements. L’équivalent avec inert est le suivant :

function openModal() {
  mainEl.inert = true;
}
function closeModal() {
  mainEl.inert = false;
}

Exemple complet de modale avec restauration du focus

Le pattern de modale personnalisée le plus propre place la boîte de dialogue comme un élément frère de <main inert> : la modale se trouve en dehors du sous-arbre inerte, elle reste donc interactive tandis que tout ce qui se trouve dans <main> est verrouillé. Ce pattern <main inert> avec élément frère suit la structure documentée par CSS-Tricks. L’exemple ci-dessous ajoute la pièce manquante que toutes les références omettent — déplacer le focus dans la boîte de dialogue à l’ouverture et le restaurer sur l’élément déclencheur à la fermeture.

<button id="open-modal" type="button">Enregistrer les modifications…</button>

<div
  id="modal"
  class="modal"
  role="dialog"
  aria-labelledby="modal-title"
  aria-modal="true"
  hidden
>
  <h2 id="modal-title">Enregistrer les modifications ?</h2>
  <p>Vos modifications non enregistrées seront perdues.</p>
  <button id="save" type="button" autofocus>Enregistrer</button>
  <button id="cancel" type="button">Ignorer</button>
</div>

<main id="page">
  <!-- tout le contenu de la page -->
</main>
const triggerEl = document.getElementById('open-modal');
const modalEl = document.getElementById('modal');
const mainEl = document.getElementById('page');
const cancelEl = document.getElementById('cancel');

let lastFocused = null;

function openModal() {
  lastFocused = document.activeElement;   // mémoriser l'élément déclencheur
  modalEl.hidden = false;
  mainEl.inert = true;                    // protéger le reste de la page
  // déplacer le focus vers l'action principale de la boîte de dialogue
  modalEl.querySelector('[autofocus]').focus();
}

function closeModal() {
  mainEl.inert = false;                   // restaurer la page
  modalEl.hidden = true;
  if (lastFocused) lastFocused.focus();   // restaurer le focus sur l'élément déclencheur
}

triggerEl.addEventListener('click', openModal);
cancelEl.addEventListener('click', closeModal);
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape' && !modalEl.hidden) closeModal();
});

Quelques remarques sur la correction. La boîte de dialogue utilise la sémantique tabindex="-1" implicitement via ses enfants focalisables ; vous n’avez généralement pas besoin d’un tabindex positif — les entiers positifs écrasent l’ordre de tabulation naturel et constituent un anti-pattern documenté. Utilisez tabindex="-1" uniquement lorsque vous devez focaliser programmatiquement un conteneur non interactif, et tabindex="0" uniquement pour des éléments personnalisés véritablement interactifs. L’attribut autofocus sur l’action principale est le point de départ recommandé par la spécification pour le focus à l’intérieur d’une boîte de dialogue. Ce pattern fonctionne dans Chrome 102+, Firefox 112+ et Safari 15.5+.

Styliser le contenu inerte : aucun style par défaut

inert n’a aucun effet visuel par défaut — le navigateur modifie le comportement, pas l’apparence, de sorte que le contenu inerte est visuellement identique au contenu actif sans mise en forme spécifique. Le pattern standard, présenté sur web.dev, cible le sélecteur d’attribut [inert] et combine trois propriétés qui reflètent les canaux d’interaction bloqués par l’attribut :

[inert],
[inert] * {
  opacity: 0.5;         /* assombrissement visuel — signale "inactif" */
  pointer-events: none; /* supprime les affordances de survol et de curseur */
  user-select: none;    /* empêche la sélection de texte */
  cursor: default;
}

Chaque propriété a sa raison d’être : opacity communique visuellement l’état désactivé, pointer-events: none supprime les états de survol et les changements de curseur qui suggèreraient autrement une interactivité, et user-select: none correspond au blocage de sélection de texte déjà appliqué par l’attribut. Le comportement est imposé par inert lui-même ; le CSS existe pour que les utilisateurs voyants puissent percevoir la frontière que le navigateur impose en coulisses.

Choisir entre inert, disabled, aria-hidden, hidden et pointer-events

Choisissez l’outil en fonction de la portée et du comportement vis-à-vis de l’arbre d’accessibilité : inert bloque tous les canaux d’interaction sur l’ensemble d’un sous-arbre et le retire de l’arbre d’accessibilité ; disabled bloque un seul contrôle mais le maintient découvrable ; aria-hidden masque le contenu des technologies d’assistance tout en laissant les clics et le focus intacts ; hidden retire entièrement le contenu ; et pointer-events: none en CSS ne bloque que la souris. Utilisez inert chaque fois que vous devez isoler le contenu en arrière-plan derrière une modale, un tiroir ou une superposition de chargement.

OutilBloque l’interactionDans l’arbre d’accessibilité ?PortéeUtiliser quand
inertOui (focus, pointeur, recherche, sélection)Non — retiréÉlément + sous-arbreIsolation du contenu en arrière-plan derrière une modale, un tiroir ou une superposition de chargement
disabledOui (pour le contrôle)Oui — annoncé comme indisponibleContrôle de formulaire ou groupe fieldsetUn bouton, une saisie ou une section de formulaire temporairement non actionnable
aria-hidden="true"Non — les clics et le focus fonctionnent toujoursNon — retiréÉlément + sous-arbreMasquer un contenu décoratif ou dupliqué aux technologies d’assistance uniquement
hidden / display:noneOui — entièrement retiréNon — non renduÉlément + sous-arbreContenu qui ne doit pas exister visuellement ni pour les technologies d’assistance à ce moment
pointer-events: noneSouris uniquement — clavier et technologies d’assistance non affectésOuiÉlément + sous-arbreClic traversant purement cosmétique ; ne remplace jamais inert

Les deux erreurs courantes : utiliser aria-hidden sur le contenu en arrière-plan tout en le laissant cliquable et focalisable (il reste dans l’ordre de tabulation), et utiliser pointer-events: none en supposant que les utilisateurs au clavier et les lecteurs d’écran sont bloqués (ce n’est pas le cas). Pour une isolation complète de l’arrière-plan, inert est le seul outil qui couvre tous les canaux.

Avez-vous encore besoin de inert avec dialog.showModal() ?

Lorsque vous ouvrez une boîte de dialogue avec HTMLDialogElement.showModal(), le navigateur rend automatiquement le reste de la page inerte — le comportement du top-layer inclut une frontière inerte implicite, de sorte que tout ce qui se trouve en dehors de la boîte de dialogue devient non cliquable et non accessible par Tab sans aucune gestion d’attribut de votre part. La gestion manuelle de inert n’est nécessaire que lorsque vous construisez un pattern de boîte de dialogue personnalisée en dehors de l’élément natif <dialog>, comme dans l’exemple complet ci-dessus.

<dialog id="confirm">
  <p>Supprimer cet élément ?</p>
  <button>Supprimer</button>
  <button>Annuler</button>
</dialog>
document.getElementById('confirm').showModal(); // page automatiquement rendue inerte

Si vous pouvez utiliser <dialog> avec showModal(), vous obtenez la frontière inerte gratuitement. Recourez à inert manuel lorsque des préoccupations de support des technologies d’assistance ou des contraintes de conception vous poussent vers une boîte de dialogue personnalisée.

Support navigateur et la propriété CSS interactivity en émergence

inert est devenu Baseline Newly available en avril 2023 — Chrome et Edge l’ont intégré à la version 102, Firefox à la version 112 et Safari à la version 15.5 — et a atteint le statut Baseline Widely available aux alentours d’octobre 2025 (30 mois après la date d’interopérabilité). Le polyfill wicg-inert relève désormais du contexte historique plutôt que d’une exigence de production ; sa dernière version est la v3.1.3 (2023) et il n’est plus activement maintenu. Son propre README indique que le polyfill est « coûteux en termes de performances » car il « nécessite un parcours important de l’arbre » — un coût que l’implémentation native évite. Pour tout navigateur sorti depuis 2023, vous n’en avez pas besoin.

Une alternative plus récente basée sur CSS est la propriété interactivity, qui accepte interactivity: inert pour appliquer le comportement inerte via une feuille de style plutôt qu’un attribut. Il s’agit d’une fonctionnalité émergente avec un support plus limité : selon les données caniuse, elle est réservée à Chromium (Chrome/Edge 135+, mars 2025) sans support Firefox ni Safari à mi-2026, et elle n’est pas Baseline (disponibilité limitée). Considérez-la comme une option prospective pour les contextes Chromium uniquement, et non comme un remplacement multi-navigateurs de l’attribut.

Conclusion

Pour isoler le contenu en arrière-plan derrière une modale, un tiroir ou une superposition de chargement, inert remplace l’ensemble du fragile appareil des pièges de focus codés manuellement et de la prolifération d’aria-hidden par un seul attribut que le navigateur impose sur la navigation au clavier, au pointeur et par technologies d’assistance. Auditez vos boîtes de dialogue existantes : si un utilisateur au clavier peut naviguer par Tab hors d’une modale ouverte vers la page en arrière-plan, encapsulez ce contenu en arrière-plan — ou votre <main> — dans inert, basculez-le avec element.inert à l’ouverture et à la fermeture, et restaurez le focus sur l’élément déclencheur. L’attribut étant Widely available depuis 2025, la seule décision restante est de savoir si le <dialog> natif vous offre la frontière gratuitement.

Questions fréquentes

Quelle est la différence entre l'attribut inert et aria-hidden ?

L'attribut inert bloque l'interaction et retire le contenu de l'arbre d'accessibilité, rendant le sous-arbre à la fois inaccessible et non découvrable. L'attribut aria-hidden retire uniquement le contenu de l'arbre d'accessibilité ; il ne bloque ni les clics, ni le focus, ni l'interaction au clavier. Appliquer aria-hidden au contenu en arrière-plan tout en le laissant cliquable et focalisable est une erreur courante, car ces éléments restent dans l'ordre de tabulation. Utilisez inert lorsque vous devez bloquer l'interaction sur l'ensemble d'un sous-arbre.

inert bloque-t-il les écouteurs d'événements JavaScript et les événements programmatiques ?

Non. L'attribut inert bloque les événements initiés par l'utilisateur tels que les clics, le focus et les interactions de pointeur, mais n'arrête pas les événements programmatiques. Un appel à dispatchEvent, un callback de timer ou tout script s'exécutant à l'intérieur d'un sous-arbre inerte s'exécute normalement. Contrairement à la fonction alert, inert ne gèle pas l'exécution JavaScript ; il modifie uniquement la façon dont le navigateur route les interactions utilisateur et la découverte d'accessibilité vers le sous-arbre marqué.

Ai-je encore besoin d'un polyfill JavaScript pour inert ?

Non, pour tout navigateur sorti depuis 2023. L'attribut inert est devenu Baseline Newly available en avril 2023, avec Chrome et Edge 102, Firefox 112 et Safari 15.5, et a atteint le statut Baseline Widely available aux alentours d'octobre 2025. Le polyfill wicg-inert relève désormais du contexte historique plutôt que d'une exigence de production ; sa dernière version est la v3.1.3 en 2023 et il n'est plus activement maintenu. Son README indique également qu'il est coûteux en termes de performances car il nécessite un parcours de l'arbre que les implémentations natives évitent.

Pourquoi les pièges de focus traditionnels échouent-ils sur les lecteurs d'écran mobiles ?

Les pièges de focus traditionnels échouent sur mobile car TalkBack sur Android et VoiceOver sur iOS naviguent par gestes de balayage plutôt que par frappes sur Tab. Un piège JavaScript qui n'intercepte que les événements clavier ne fournit aucune frontière pour les utilisateurs de lecteurs d'écran naviguant par balayage, qui peuvent donc sortir de la boîte de dialogue vers le contenu en arrière-plan. L'attribut inert bloque le sous-arbre au niveau de la plateforme, couvrant à la fois la navigation au clavier et la navigation par gestes, ce qui explique pourquoi il remplace la logique de piège de focus codée manuellement pour délimiter les modales.

Digital experience platform

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.