12k
All articles

Gestión del Foco e Interactividad con el Atributo Inert

Usa el atributo inert para aislar modales, paneles y overlays de carga, bloqueando foco, clics y acceso al árbol de accesibilidad.

OpenReplay Team
OpenReplay Team
Gestión del Foco e Interactividad con el Atributo Inert

Establecer inert en un elemento elimina todo su subárbol del orden de tabulación, bloquea todos los eventos de puntero y clic, y lo oculta del árbol de accesibilidad para que los lectores de pantalla no puedan descubrirlo ni anunciarlo — estos tres comportamientos están definidos normativamente por el Estándar de Vida HTML de WHATWG. Además, los navegadores actuales impiden que la búsqueda en página (Ctrl/Cmd+F) encuentre coincidencias en el texto del subárbol y deshabilitan la selección de texto dentro de él; comportamientos que la especificación deja a discreción del agente de usuario, pero que MDN documenta como la norma implementada. En total, seis canales de interacción deshabilitados con un solo atributo.

Este artículo resuelve un problema concreto: cómo deshabilitar limpiamente el contenido en segundo plano cuando se abre un modal, un drawer o un menú lateral — sin tener que construir manualmente un focus trap que falla en los lectores de pantalla móviles, ni dispersar aria-hidden por todo el DOM. Se cubre qué bloquea inert, la sintaxis HTML y JavaScript para aplicarlo, un modal funcional completo con restauración del foco, cómo aplicar estilos al contenido inerte, y cuándo recurrir a disabled, aria-hidden o hidden en su lugar.

Puntos Clave

  • inert bloquea seis canales de interacción en una sola declaración: foco, eventos de puntero/clic, orden de tabulación y descubribilidad en el árbol de accesibilidad (todos normativos según la especificación), además de la búsqueda en página y la selección de texto (a discreción del agente de usuario, pero implementados en los navegadores actuales).
  • inert pasó a ser Baseline Newly available en abril de 2023 — Chrome y Edge 102, Firefox 112, Safari 15.5 — y alcanzó el estado Baseline Widely available alrededor de octubre de 2025, lo que convierte al polyfill wicg-inert en contexto heredado y no en un requisito de producción.
  • El cambio de modelo mental es guardia frente a trampa: un focus trap encierra a los usuarios dentro de un componente mediante JavaScript; inert protege el resto de la página para que el navegador imponga el límite de forma nativa.
  • Más allá del atributo booleano HTML, inert está expuesto como la propiedad IDL HTMLElement.inert, un booleano que se establece en JavaScript — mainEl.inert = true al abrir, mainEl.inert = false al cerrar.
  • <dialog>.showModal() aplica inert automáticamente al resto de la página, por lo que la gestión manual de inert solo es necesaria para patrones de diálogo personalizados construidos fuera del elemento nativo.

Qué Bloquea el Atributo inert

inert es un atributo HTML global que hace que un elemento y todo su subárbol sean no interactivos e imposibles de descubrir. Según la sección de subárboles inertes del Estándar de Vida HTML de WHATWG, un nodo inerte no recibe eventos de interacción de usuario dirigidos, como click y focus, y los agentes de usuario deben excluirlo del árbol de accesibilidad. Tres bloqueos son normativos y consistentes en todas las implementaciones:

  1. Foco — los elementos inertes no pueden recibir el foco mediante Tab, clic o el método programático element.focus().
  2. Eventos de puntero y clic — los clics iniciados por el usuario y los eventos de puntero no llegan a los nodos inertes.
  3. Descubribilidad en el árbol de accesibilidad — las tecnologías de asistencia no pueden encontrar ni anunciar el subárbol.

Dos bloqueos adicionales quedan a discreción del agente de usuario en la especificación (que utiliza el normativo may), pero MDN los documenta como el comportamiento implementado en los navegadores actuales:

  1. Búsqueda en página — Ctrl/Cmd+F no encuentra coincidencias en el texto dentro de un subárbol inerte.
  2. Selección de texto — los usuarios no pueden seleccionar texto inerte.

El orden de tabulación se deriva del bloqueo de foco: dado que los nodos inertes no pueden recibir el foco, quedan excluidos por completo de la navegación secuencial por teclado. Un límite importante: inert bloquea los eventos iniciados por el usuario, no los programáticos. Una llamada a dispatchEvent() o un temporizador que se ejecute dentro de un subárbol inerte sigue funcionando con normalidad — inert no es alert() y no congela la ejecución de JavaScript.

El error conceptual que hay que interiorizar: dado que inert elimina el subárbol del árbol de accesibilidad, nunca debe aplicarse a contenido que el usuario todavía necesite leer. Si solo se necesita ocultar algo visualmente pero mantenerlo descubrible, esa es una herramienta diferente.

Los Dos Casos de Uso Canónicos

inert existe para dos situaciones, ambas documentadas en la guía de inert de web.dev: DOM que está presente pero fuera de pantalla u oculto, y DOM que es visible pero no debería ser interactivo.

DOM fuera de pantalla u oculto. Un drawer de navegación deslizante o un menú lateral añade enlaces enfocables al DOM antes de que sean visibles. Sin inert, los usuarios de teclado pueden tabular hacia el drawer cerrado y llegar a controles que no pueden ver. Marcar el contenedor del drawer como inerte hasta que se abra mantiene esos enlaces fuera del orden de tabulación:

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

UI visible pero no interactiva. Cuando un formulario se está enviando, una página está cargando, o una superposición modal cubre el contenido en segundo plano, ese contenido está visiblemente presente pero no debería aceptar entradas. Aplicar inert al formulario durante el envío previene envíos duplicados y el desplazamiento involuntario del foco:

<form id="signup" inert>
  <!-- campos deshabilitados como grupo mientras la solicitud está en curso -->
</form>

Ambos casos comparten la misma lógica: el contenido permanece en el DOM (para que el layout, las transiciones y el estado se conserven), pero el navegador se niega a enrutar la interacción hacia él.

Sintaxis: El Atributo HTML y la Propiedad HTMLElement.inert

inert tiene dos interfaces: el atributo booleano HTML y la propiedad IDL HTMLElement.inert. El atributo es para el marcado estático o renderizado en el servidor; la propiedad es para alternar el estado en JavaScript.

Como atributo booleano, lo que importa es su presencia — inert e inert="" son equivalentes, y el valor predeterminado es false (ausente significa interactivo):

<main inert>
  <!-- todo lo que hay aquí es no interactivo -->
</main>

Para alternarlo en tiempo de ejecución, se utiliza la propiedad HTMLElement.inert, un booleano que se puede leer y establecer directamente — sin necesidad de usar setAttribute / removeAttribute:

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

// deshabilitar la interacción con el resto de la página
mainEl.inert = true;

// restaurarla
mainEl.inert = false;

Esta es la parte más limpia de la API y la que falta en la mayoría de los artículos existentes: el interruptor de apertura/cierre son dos asignaciones. Compárese con el procedimiento de ocho pasos de un focus trap que se describe a continuación.

Antes de inert: Los Focus Traps y Por Qué Eran Frágiles

Antes de inert, la forma estándar de delimitar un modal era un focus trap en JavaScript — lógica que intercepta Tab y Shift+Tab para mantener el foco dentro del diálogo. El procedimiento canónico, enumerado por CSS-Tricks, comprende aproximadamente ocho pasos: encontrar todos los elementos enfocables de la página, identificar el primero y el último elemento enfocable dentro del modal, eliminar la interactividad y la descubribilidad de todo lo que está fuera de él, mover el foco hacia dentro, escuchar los eventos de cierre, restaurar todo al cerrar, y devolver el foco al elemento que lo activó.

Ese primer paso — “encontrar todos los elementos enfocables” — es en sí mismo una fuente de errores, porque el conjunto de elementos nativamente enfocables es más amplio de lo que la mayoría recuerda. Los elementos que reciben el foco en el orden de tabulación secuencial sin ningún tabindex son:

  • <a> y <area> con un atributo href
  • <button>, <input>, <select> y <textarea> (salvo que estén disabled)
  • <iframe>, <embed> y <object>
  • <audio> y <video> con el atributo controls
  • <summary> (el primero dentro de un <details>)
  • cualquier elemento con un tabindex no negativo, y cualquier elemento contenteditable

Omitir uno al construir un trap permite que un usuario de teclado escape de él; gestionar la lista en exceso implica luchar contra el propio orden del navegador. La opción más segura por defecto es dejar intacto el orden de foco natural del documento e intervenir solo cuando un componente lo requiera genuinamente — que es la mayor parte del trabajo manual que inert elimina.

El cambio de modelo mental es el punto central. Un focus trap encierra a los usuarios dentro de un componente interceptando pulsaciones de teclas; inert protege el resto de la página haciendo que todo lo que está fuera del diálogo sea inalcanzable — el navegador impone el límite, no el código JavaScript. Este enfoque de guardia frente a trampa proviene del análisis del atributo realizado por LogRocket.

Los traps construidos manualmente fallan de tres formas recurrentes:

  • Tecnología de asistencia móvil. TalkBack en Android y VoiceOver en iOS navegan mediante gestos de deslizamiento, no mediante pulsaciones de la tecla Tab. Un trap en JavaScript que solo intercepta eventos de teclado no proporciona ningún límite para los usuarios de lectores de pantalla que navegan por gestos. inert bloquea el subárbol a nivel de plataforma, cubriendo tanto la navegación por teclado como la navegación por gestos.
  • Proliferación de aria-hidden. La solución anterior a inert consistía en establecer aria-hidden="true" en cada elemento que no fuera el modal. En páginas con árboles DOM profundos, esto se vuelve imposible de mantener y con frecuencia queda incompleto.
  • Bucles de tabulación manuales. La lógica de intercepción de Tab/Shift+Tab es frágil y fácil de implementar incorrectamente, especialmente cuando el contenido enfocable del modal cambia.

Las repeticiones de sesión de implementaciones de modales y drawers frecuentemente revelan eventos de foco que aterrizan en el contenido en segundo plano mientras un diálogo sigue abierto — la señal característica de un límite de foco incompleto, y precisamente lo que inert está diseñado para eliminar.

Las referencias anteriores reconstruyen un trap-focus.js completo; no es necesario repetirlo aquí. La comparación relevante es el número de líneas. El trap ocupa decenas de líneas de intercepción de eventos. El equivalente con inert es este:

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

Ejemplo Funcional de Modal con Restauración del Foco

El patrón de modal personalizado más limpio coloca el diálogo como hermano de <main inert>: el modal se sitúa fuera del subárbol inerte, por lo que permanece interactivo mientras todo lo que hay en <main> queda bloqueado. Este patrón de hermano <main inert> sigue la estructura documentada por CSS-Tricks. El ejemplo a continuación añade la pieza que todas las referencias omiten — mover el foco al diálogo al abrirlo y restaurarlo al elemento que lo activó al cerrarlo.

<button id="open-modal" type="button">Guardar cambios…</button>

<div
  id="modal"
  class="modal"
  role="dialog"
  aria-labelledby="modal-title"
  aria-modal="true"
  hidden
>
  <h2 id="modal-title">¿Guardar cambios?</h2>
  <p>Los cambios no guardados se perderán.</p>
  <button id="save" type="button" autofocus>Guardar</button>
  <button id="cancel" type="button">Descartar</button>
</div>

<main id="page">
  <!-- todo el contenido de la página -->
</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;   // recordar el elemento activador
  modalEl.hidden = false;
  mainEl.inert = true;                    // proteger el resto de la página
  // mover el foco a la acción principal del diálogo
  modalEl.querySelector('[autofocus]').focus();
}

function closeModal() {
  mainEl.inert = false;                   // restaurar la página
  modalEl.hidden = true;
  if (lastFocused) lastFocused.focus();   // restaurar el foco al elemento activador
}

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

Algunas notas sobre la corrección. El diálogo utiliza la semántica de tabindex="-1" implícitamente a través de sus hijos enfocables; en general no es necesario un tabindex positivo en ningún lugar — los enteros positivos anulan el orden de tabulación natural y son un antipatrón documentado. Use tabindex="-1" solo cuando necesite enfocar programáticamente un contenedor no interactivo, y tabindex="0" únicamente para elementos personalizados genuinamente interactivos. El atributo autofocus en la acción principal es el punto de partida recomendado por la especificación para el foco dentro de un diálogo. Este patrón funciona en Chrome 102+, Firefox 112+ y Safari 15.5+.

Aplicar Estilos al Contenido Inerte: No Hay Estilo por Defecto

inert no tiene ningún efecto visual predeterminado — el navegador cambia el comportamiento, no la apariencia, por lo que el contenido inerte luce idéntico al contenido activo a menos que se le apliquen estilos. El patrón estándar, mostrado en web.dev, apunta al selector de atributo [inert] y combina tres propiedades que reflejan los canales de interacción que bloquea el atributo:

[inert],
[inert] * {
  opacity: 0.5;         /* atenuación visual — indica "no activo" */
  pointer-events: none; /* suprime los estados hover y los indicadores del cursor */
  user-select: none;    /* impide la selección de texto */
  cursor: default;
}

Cada propiedad cumple su función: opacity comunica visualmente el estado deshabilitado, pointer-events: none elimina los estados hover y los cambios de cursor que de otro modo implicarían interactividad, y user-select: none complementa el bloqueo de selección de texto que el atributo ya aplica. El comportamiento lo impone inert en sí mismo; el CSS existe para que los usuarios videntes puedan ver el límite que el navegador está aplicando internamente.

Elegir Entre inert, disabled, aria-hidden, hidden y pointer-events

Elija la herramienta según el alcance y el comportamiento en el árbol de accesibilidad: inert bloquea todos los canales de interacción en un subárbol completo y lo elimina del árbol de accesibilidad; disabled bloquea un único control pero lo mantiene descubrible; aria-hidden oculta el contenido de las tecnologías de asistencia mientras deja los clics y el foco intactos; hidden elimina el contenido por completo; y pointer-events: none en CSS bloquea únicamente el ratón. Recurra a inert siempre que necesite aislar el contenido en segundo plano detrás de un modal, un drawer o una superposición de carga.

HerramientaBloquea la interacción¿En el árbol de accesibilidad?AlcanceCuándo usarla
inertSí (foco, puntero, búsqueda, selección)No — eliminadoElemento + subárbolAislar el contenido en segundo plano detrás de un modal, drawer o superposición de carga
disabledSí (para el control)Sí — anunciado como no disponibleControl de formulario o grupo fieldsetUn botón, input o sección de formulario que temporalmente no es accionable
aria-hidden="true"No — los clics y el foco siguen funcionandoNo — eliminadoElemento + subárbolOcultar contenido decorativo o duplicado solo de las tecnologías de asistencia
hidden / display:noneSí — completamente eliminadoNo — no renderizadoElemento + subárbolContenido que no debería existir visualmente ni para las tecnologías de asistencia en este momento
pointer-events: noneSolo ratón — teclado y tecnologías de asistencia no se ven afectadosElemento + subárbolPermitir clics a través de un elemento de forma cosmética; nunca como sustituto de inert

Los dos errores más comunes: usar aria-hidden en el contenido en segundo plano dejándolo clickeable y enfocable (permanece en el orden de tabulación), y usar pointer-events: none asumiendo que los usuarios de teclado y lectores de pantalla también quedan bloqueados (no es así). Para un aislamiento completo del fondo, inert es la única herramienta individual que cubre todos los canales.

¿Sigue Siendo Necesario inert con dialog.showModal()?

Cuando se abre un diálogo con HTMLDialogElement.showModal(), el navegador aplica automáticamente inert al resto de la página — el comportamiento de la capa superior incluye un límite inerte implícito, por lo que todo lo que está fuera del diálogo se vuelve no clickeable e intabulable sin ninguna gestión de atributos por parte del desarrollador. La gestión manual de inert solo es necesaria cuando se construye un patrón de diálogo personalizado fuera del elemento nativo <dialog>, como en el ejemplo funcional anterior.

<dialog id="confirm">
  <p>¿Eliminar este elemento?</p>
  <button>Eliminar</button>
  <button>Cancelar</button>
</dialog>
document.getElementById('confirm').showModal(); // la página queda inerte automáticamente

Si es posible usar <dialog> con showModal(), el límite inerte se obtiene de forma gratuita. Recurra a inert manual cuando las preocupaciones de compatibilidad con tecnologías de asistencia o las restricciones de diseño obliguen a optar por un diálogo personalizado.

Compatibilidad con Navegadores y la Propiedad CSS interactivity en Desarrollo

inert pasó a ser Baseline Newly available en abril de 2023 — Chrome y Edge lo implementaron en la versión 102, Firefox en la 112 y Safari en la 15.5 — y alcanzó el estado Baseline Widely available alrededor de octubre de 2025 (30 meses después de la fecha de interoperabilidad). El polyfill wicg-inert es ahora contexto heredado y no un requisito de producción; su última versión es la v3.1.3 (2023) y ya no se mantiene activamente. Su propio README señala que el polyfill es “costoso en términos de rendimiento” porque “requiere una cantidad considerable de recorrido del árbol” — un coste que la implementación nativa evita. Para cualquier navegador lanzado desde 2023, no es necesario.

Una alternativa más reciente basada en CSS es la propiedad interactivity, que acepta interactivity: inert para aplicar el comportamiento inerte a través de una hoja de estilos en lugar de un atributo. Se trata de una funcionalidad emergente con una compatibilidad más limitada: según los datos de caniuse, es exclusiva de Chromium (Chrome/Edge 135+, marzo de 2025), sin soporte en Firefox ni Safari a mediados de 2026, y no forma parte de Baseline (disponibilidad limitada). Trátela como una opción orientada al futuro para contextos exclusivos de Chromium, no como un reemplazo multiplataforma del atributo.

Conclusión

Para aislar el contenido en segundo plano detrás de un modal, un drawer o una superposición de carga, inert reemplaza todo el frágil aparato de focus traps construidos manualmente y la proliferación de aria-hidden con un único atributo que el navegador impone en la navegación por teclado, puntero y tecnologías de asistencia. Audite sus diálogos existentes: si un usuario de teclado puede tabular fuera de un modal abierto hacia la página que hay detrás, envuelva ese contenido en segundo plano — o su <main> — con inert, actívelo y desactívelo con element.inert al abrir y cerrar, y restaure el foco al elemento activador. Con el atributo en estado Widely available desde 2025, la única decisión que queda es si el <dialog> nativo le proporciona el límite de forma gratuita.

Preguntas Frecuentes

¿Cuál es la diferencia entre el atributo inert y aria-hidden?

El atributo inert bloquea la interacción y elimina el contenido del árbol de accesibilidad, por lo que el subárbol se vuelve tanto inalcanzable como imposible de descubrir. El atributo aria-hidden solo elimina el contenido del árbol de accesibilidad; no bloquea los clics, el foco ni la interacción por teclado. Aplicar aria-hidden al contenido en segundo plano dejándolo clickeable y enfocable es un error común, ya que esos elementos permanecen en el orden de tabulación. Use inert cuando necesite bloquear la interacción en todo un subárbol.

¿Bloquea inert los event listeners de JavaScript y los eventos programáticos?

No. El atributo inert bloquea los eventos iniciados por el usuario, como clics, foco e interacción con el puntero, pero no detiene los eventos programáticos. Una llamada a dispatchEvent, un callback de temporizador o cualquier script que se ejecute dentro de un subárbol inerte sigue ejecutándose con normalidad. A diferencia de la función alert, inert no congela la ejecución de JavaScript; solo cambia la forma en que el navegador enruta la interacción del usuario y el descubrimiento de accesibilidad hacia el subárbol marcado.

¿Sigo necesitando un polyfill de JavaScript para inert?

No, para cualquier navegador lanzado desde 2023. El atributo inert pasó a ser Baseline Newly available en abril de 2023, con Chrome y Edge 102, Firefox 112 y Safari 15.5, y alcanzó el estado Baseline Widely available alrededor de octubre de 2025. El polyfill wicg-inert es ahora contexto heredado y no un requisito de producción; su última versión es la v3.1.3 de 2023 y ya no se mantiene activamente. Su README también señala que es costoso en términos de rendimiento porque requiere el recorrido del árbol que las implementaciones nativas evitan.

¿Por qué los focus traps tradicionales fallan en los lectores de pantalla móviles?

Los focus traps tradicionales fallan en móviles porque TalkBack en Android y VoiceOver en iOS navegan mediante gestos de deslizamiento en lugar de pulsaciones de la tecla Tab. Un trap en JavaScript que intercepta únicamente eventos de teclado no proporciona ningún límite para los usuarios de lectores de pantalla que navegan por gestos, por lo que pueden escapar del diálogo hacia el contenido en segundo plano. El atributo inert bloquea el subárbol a nivel de plataforma, cubriendo tanto la navegación por teclado como la navegación por gestos, que es la razón por la que reemplaza la lógica de focus trap construida manualmente para delimitar 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.