HTML
    advancedmodern

    The inert Attribute: Controlling Accessibility Exposure Without CSS Hacks

    The inert Attribute: Controlling Accessibility Exposure Without CSS Hacks

    Editor: Paul RadfordJun 5, 20267 min read
    The inert Attribute: Controlling Accessibility Exposure Without CSS Hacks

    The inert attribute removes an element and its descendants from the accessibility tree, tab order, and pointer/click interaction in one line of HTML. It replaces the old combination of tabindex="-1", aria-hidden="true", and pointer-events: none, which developers stacked together for years to fake the same behavior, often incorrectly.

    What Problem Was inert Actually Solving?

    Before inert shipped, hiding a section of the page from assistive technology while a modal or drawer was open required juggling three separate mechanisms:

    • aria-hidden="true" to hide content from screen readers
    • tabindex="-1" applied recursively to every focusable descendant
    • pointer-events: none in CSS to block mouse and touch interaction

    Each of these solves one slice of the problem, and each has a failure mode. aria-hidden alone does nothing to stop keyboard focus, so a screen reader user can still tab into a "hidden" element and get read content that visually doesn't make sense, or worse, submit a form field nobody can see. tabindex="-1" has to be applied to every focusable child individually, and you have to remember to remove it later, or track which elements already had a tabindex value so you don't clobber it. pointer-events: none blocks mouse clicks but does nothing about keyboard or screen reader interaction at all.

    inert collapses all three concerns into a single boolean attribute. When present, the browser:

    • Removes the subtree from the accessibility tree (equivalent to aria-hidden, but enforced by the browser rather than left to AT vendors to interpret)
    • Skips the subtree during sequential keyboard navigation
    • Ignores pointer and mouse events on the subtree, including clicks that would otherwise bubble

    That's the whole spec surface. It's defined in the WHATWG HTML spec as a global attribute usable on any element.

    Basic Usage: Locking Background Content Behind a Modal

    The canonical use case is a dialog or modal that should be the only interactive thing on screen while it's open.

    html
    1<!-- Main content gets inert while the modal is open -->
    2<main id="page-content">
    3 <h1>Dashboard</h1>
    4 <button id="open-modal">Open settings</button>
    5 <!-- ... rest of the page ... -->
    6</main>
    7
    8<div id="settings-modal" role="dialog" aria-modal="true" hidden>
    9 <h2>Settings</h2>
    10 <label>Email notifications
    11 <input type="checkbox">
    12 </label>
    13 <button id="close-modal">Close</button>
    14</div>
    javascript
    1// Toggle inert on the background content, not on the modal itself
    2const main = document.getElementById('page-content');
    3const modal = document.getElementById('settings-modal');
    4const openBtn = document.getElementById('open-modal');
    5const closeBtn = document.getElementById('close-modal');
    6
    7openBtn.addEventListener('click', () => {
    8 modal.hidden = false;
    9 main.inert = true; // background becomes untouchable and invisible to AT
    10 closeBtn.focus(); // move focus into the modal manually, inert does not do this
    11});
    12
    13closeBtn.addEventListener('click', () => {
    14 modal.hidden = true;
    15 main.inert = false;
    16 openBtn.focus(); // return focus to the trigger
    17});

    Note the manual focus management. inert stops focus from leaking into the background, but it does not move focus into the modal for you, and it does not implement a focus trap by itself. You still need role="dialog", aria-modal="true", and explicit focus handling on open and close. inert handles the "don't let anything else be reachable" half of the problem, not the "manage focus inside the dialog" half.

    Does inert Actually Replace aria-hidden?

    Mostly, but not in every case, and this is where people get tripped up. aria-hidden="true" only affects the accessibility tree. It has no effect on focus or pointer events, which is exactly the gap that caused so many bugs. inert is a superset of that behavior for the specific case of "this whole subtree should be off limits."

    Where they diverge: you might want something hidden from screen readers but still clickable, for example a purely decorative overlay that sits on top of real content for visual effect but shouldn't block interaction. aria-hidden alone still has a place there because it doesn't touch pointer events or focus. You'd reach for inert only when you want the full lockout: no accessibility exposure, no focus, no clicks.

    Also worth knowing: setting inert on an element does not add aria-hidden="true" to the DOM. It's a distinct mechanism that produces an equivalent accessibility tree result. If you inspect the accessibility tree in Chrome DevTools you'll see the subtree marked as ignored, but you won't find an aria-hidden attribute if you only set inert. Don't write code that checks for aria-hidden and expects it to catch elements that are actually inert.

    Styling inert Elements

    Browsers apply a default opacity dimming or similar visual treatment to inert content in some implementations, but you shouldn't rely on that being consistent or even present. The spec does define a UA stylesheet suggestion, but visual treatment is not guaranteed cross-browser. If you want a dimmed or grayed-out look for inert background content, style it explicitly:

    css
    1/* Make the visual treatment explicit instead of relying on browser defaults */
    2[inert] {
    3 opacity: 0.5;
    4 cursor: not-allowed;
    5 user-select: none;
    6}

    Also remember that inert is a boolean attribute you can select in CSS with [inert], which is convenient for exactly this kind of styling hook.

    When NOT to Reach for inert

    inert is not a substitute for display: none or hidden when content should be fully removed from the layout. An inert element still renders, still takes up space, and still occupies its position in the DOM. If you actually want to hide content and don't need to keep it visually present, use hidden or display: none, which also remove accessibility exposure as a side effect and are cheaper to reason about.

    Don't apply inert to the entire <body> as a lazy way to lock the page during an async operation. It works, but it also kills focus restoration edge cases and can strand focus in strange ways if you forget to except the loading indicator itself. Scope it as tightly as you can, ideally to the single container you're trying to neutralize.

    Also skip inert for form validation states. Marking an invalid field's container as inert to "force" users to fix something before proceeding is hostile and breaks expectations. Use aria-invalid, aria-describedby pointing at an error message, and native required or constraint validation instead.

    Finally, don't nest redundant inert attributes expecting some kind of override behavior. If a parent is inert, children are inert regardless of their own attribute value, and toggling a child's inert attribute independently while the parent stays inert has no visible effect until the parent is un-inerted.

    Browser Support and a Real Gotcha

    As of 2026, inert is supported in Chrome and Edge since version 102, Firefox since version 112, and Safari since 15.5. You can confirm current numbers on caniuse.com/inert. For anything targeting older Safari or Firefox ESR releases still in the field, load the WICG inert polyfill and feature-detect with 'inert' in HTMLElement.prototype before assuming native behavior.

    The gotcha that catches people: inert does not automatically stop focus() calls made programmatically from JavaScript in every browser version. Early implementations differed on whether a script calling .focus() directly on an inert element's descendant would be blocked. Current stable browsers do block it correctly per spec, but if you support anything from 2022 or earlier Chrome/Edge builds, test this explicitly rather than assuming parity. Also, inert does not stop CSS :focus styles from applying if focus somehow lands on the element through means the browser doesn't intercept, so don't use presence of :focus styling as your only signal that something went wrong.

    Testing It Properly

    Don't just eyeball whether a modal looks right. Open DevTools, use the accessibility tree inspector (Chrome and Firefox both expose this in their DevTools panels), and confirm the background subtree shows as ignored when your modal is open. Then physically tab through the page with the modal open: focus should never land on background controls. Then test with a screen reader, VoiceOver on Safari or NVDA on Firefox/Chrome, and confirm you can't navigate into the background content using the screen reader's own navigation commands, not just the Tab key, since virtual cursor navigation in screen readers can behave differently from raw keyboard tabbing. If all three checks pass, you've verified the three things inert promises: no accessibility exposure, no focus, no interaction. Anything less than that trio of checks and you're guessing.

    Advertisement

    728 × 90 — Leaderboard — Google AdSense

    Related Articles