Before inert existed, making background content unreachable while a custom overlay was open required manually managing tabindex="-1" on every focusable descendant and aria-hidden on the container ā fragile, easy to get wrong, and easy to forget to undo. inert replaces all of that with one attribute.
1What inert Actually Removes
inert on a container makes every element inside it ā the container included ā unfocusable (Tab skips over it entirely, and explicit tabindex values are overridden), unclickable in the sense that normal pointer interaction doesn't trigger it, and excluded from the accessibility tree, so screen readers don't announce or navigate into it. Text selection is also blocked within an inert subtree.
Critically, none of this touches visual rendering. An inert element is still painted exactly as it would be otherwise, still occupies its normal layout space, and remains visible on screen ā inert is purely an interaction and accessibility-tree concern, which is precisely what makes it correct for 'dim the background but keep it visible' overlay patterns.
2Why Neither disabled Nor hidden Is The Right Tool Here
disabled only applies to a specific set of form-associated elements (<button>, <input>, <select>, <textarea>, <fieldset>) ā it can't be applied to an arbitrary container like a <main> or <div> to block an entire subtree, and disabled elements remain both visible and announced (as disabled) to assistive technology, which is a different intent than 'temporarily unreachable.'
hidden removes an element from rendering and the accessibility tree entirely ā genuinely gone, not just unreachable. This rules it out for any case where the content needs to remain visually present (dimmed background content behind an overlay, for instance) ā hiding it would defeat the entire visual purpose. inert fills exactly the gap between these two: full interaction and accessibility-tree removal, with rendering completely untouched.
3inert's Relationship To The Native <dialog> Element
A <dialog> opened via showModal() already makes the rest of the document inert automatically as a built-in part of its modal behavior ā manually adding inert to the page body alongside it is redundant. inert earns its place specifically in scenarios *not* using the native <dialog> element: a custom off-canvas navigation drawer, a non-modal-but-visually-blocking panel, or any bespoke overlay component where you're managing top-layer-like behavior yourself.
In those custom cases, toggling element.inert = true/false (it's also reflected as a JS property, not just an HTML attribute) alongside your open/close state is the direct native replacement for the older pattern of manually walking the DOM to set tabindex="-1" on every focusable descendant and restoring it afterward.
4Focus Handling And The Performance Case For inert
Because inert is enforced by the browser's own focus and hit-testing algorithms rather than JavaScript event listeners, calling .focus() on a descendant of an inert element is a silent no-op, and any element that already held focus when its ancestor became inert has that focus automatically moved elsewhere ā eliminating an entire class of 'focus trapped in invisible/unreachable content' bugs that used to require careful manual handling.
There's also a meaningful performance dimension: browsers can use an inert subtree as a signal to skip certain accessibility-tree computation and hit-testing work for that region, since it's guaranteed non-interactive ā a native, browser-level optimization that a hand-rolled 'set tabindex=-1 everywhere' approach doesn't provide.
5Step-by-Step Breakdown
Visible, But Completely Unreachable. A custom off-canvas menu, a non-native overlay, or dimmed background content behind a non-<dialog> panel all need the same thing: still visible, but entirely unreachable by mouse, keyboard, and screen reader. That's precisely what the inert attribute does ā and nothing else does.
inert Removes A Subtree From Focus, Click, And AT Interaction. Adding inert to a container makes every descendant unfocusable (even via explicit tabindex), unclickable in terms of normal interaction, and excluded from the accessibility tree that screen readers query ā while remaining fully visible and still occupying layout space.
What inert Actually Does. What happens to an element's visibility when it becomes inert?
- āNothing ā it remains fully visible; only interaction and accessibility-tree presence change
- āIt becomes invisible, equivalent to display: none
- āThe browser automatically dims it visually
inert vs disabled vs hidden: Three Distinct Tools. disabled only applies to form controls and specific interactive elements, still leaves them visible and announced (as disabled) to AT. hidden removes an element from rendering AND accessibility entirely ā it's genuinely gone. inert sits between them: visible, present in the a11y tree's absence sense (excluded from it, like hidden), but unlike hidden, still occupies layout and renders normally.
inert vs disabled vs hidden. You need to visually dim the background page content behind a custom (non-<dialog>) overlay panel, while making it fully unreachable. Which attribute fits best?
- āinert ā keeps it rendered (so CSS can dim it) but unreachable
- āhidden ā removes it from rendering entirely
- ādisabled ā but this only works on form controls, not arbitrary content
<dialog> Auto-Inerts The Rest Of The Page ā You Rarely Need inert With It. A common point of confusion: showModal() already makes the rest of the document inert automatically. Manually adding inert is for custom, non-<dialog> overlays ā an off-canvas nav drawer, a non-modal-but-visually-blocking panel ā where you're not using the native dialog element and its built-in behavior.
inert And <dialog>. Do you typically need to manually set inert on the background when using a native <dialog> with showModal()?
- āNo ā showModal() already makes the rest of the document inert automatically
- āYes, it must always be added manually alongside showModal()
- āOnly in one specific browser as a workaround
Focus Cannot Land Inside An inert Subtree, Even Programmatically. Calling .focus() on an element inside an inert container fails silently ā focus simply doesn't move there ā and if an element already had focus when its ancestor became inert, focus is automatically moved elsewhere (typically to the document body), preventing a confusing state where focus is trapped inside content the user can't perceive as interactive.
Programmatic Focus And inert. What happens if JavaScript calls .focus() on a button that's inside an inert container?
- āThe call has no effect ā focus does not move to that element
- āIt throws a JavaScript error
- āIt focuses the element anyway, ignoring inert
Progressive Enhancement: inert Has Excellent Support, But Verify Your Baseline. inert reached broad native support across current major browsers, but for a codebase needing to support notably older browser versions, a small polyfill exists ā worth checking your project's actual supported-browser matrix before assuming zero fallback work is needed.
inert Browser Support. What should you check before relying on inert with zero fallback in a production project?
- āYour project's actual minimum supported browser versions
- āNothing ā it's been universally supported since HTML's earliest days
- āWhether your server-side framework supports it, which is unrelated
inert Mastered. You now understand exactly what inert does ā removing focus, click, and accessibility-tree presence while leaving visual rendering untouched ā how it differs from disabled and hidden, and why <dialog> rarely needs it applied manually.
Disable An Entire Subtree. The inert attribute removes an element and its children from focus and interaction entirely.
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1inert Is An Accessibility-Tree Mechanism, Not Just A Focus Mechanism
It removes the subtree from what screen readers can navigate into via any means (not just Tab, but virtual cursor / browse mode navigation too), matching the visual intent of 'you can see it, but it's not currently interactive.'
SEO Implications
- 1
inert Content Is Still Present In The DOM And Crawlable
Unlike hidden, inert doesn't remove content from the DOM or from what crawlers see in rendered HTML ā it only affects live user interaction and the accessibility tree, so it has no direct SEO indexing implication.
Best Practices
Pair inert With A Visual Treatment (Dimming, Blur) So Sighted Users Perceive The State Too
inert alone changes nothing visually ā without an accompanying CSS treatment, sighted mouse/keyboard users would have no visual cue that the background content is currently unreachable.
Toggle inert As A JS Property In Sync With Your Overlay's Open/Closed State
element.inert = isOpen keeps the interaction-blocking state trivially synchronized with your component's actual open/closed logic, avoiding drift.
Frequent Bugs
A custom off-canvas menu is open, but Tab still cycles through background page content behind it.
Set inert on the background container while the drawer is open, instead of manually managing tabindex on every focusable descendant.
Manually adding inert to document.body alongside a native <dialog>'s showModal(), causing redundant/conflicting inert state.
Remove the manual inert call ā showModal() already makes the rest of the document inert automatically as built-in behavior.
Real-World Examples
A Custom Off-Canvas Navigation Drawer
A mobile nav drawer (not a <dialog>) that must make main content unreachable while open, with a visual dimming overlay.
function toggleDrawer(open) {
drawer.classList.toggle("open", open);
mainContent.inert = open;
mainContent.classList.toggle("dimmed", open);
}