CSS interactivity: inert and the inert attribute

Both make part of a page impossible to click, focus or Tab into while it stays on screen. The attribute is set by a script; the CSS property is set by a selector.

To stop people from clicking, focusing or tabbing into part of a page, add the inert attribute to it. A newer CSS property, interactivity: inert, does the same from a stylesheet. The region stays visible; it just cannot be used.

Press the blue button, then try the Save button and the field.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The inert attribute</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .bar { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 12px; }
  button, input { font: inherit; padding: 8px 12px; border-radius: 8px; border: 1px solid #c9ced8; }
  button { background: #fff; cursor: pointer; }
  #toggle { background: #1d4ed8; color: #fff; border-color: #1d4ed8; }
  .card { background: #fff; border-radius: 12px; padding: 14px 16px; box-shadow: 0 4px 14px rgba(0, 0, 0, .08); }
  .card h3 { margin: 0 0 10px; font-size: 16px; }
  .card input { width: 100%; box-sizing: border-box; margin-bottom: 10px; }
  /* inert does not change the look, so show it yourself */
  .card[inert] { opacity: .45; }
  code { background: #e8ebf0; border-radius: 4px; padding: 1px 5px; }
</style>
</head>
<body>
<div class="bar">
  <button id="toggle">Make the card inert</button>
  <span>card.inert = <code id="state">false</code></span>
</div>

<section class="card" id="card">
  <h3>Shipping address</h3>
  <input id="street" placeholder="Street" aria-label="Street">
  <button id="save">Save (clicked <span id="n">0</span>)</button>
</section>

<script>
  const card = document.getElementById('card');
  const toggle = document.getElementById('toggle');
  let clicks = 0;

  toggle.addEventListener('click', () => {
    card.inert = !card.inert;  // same as adding or removing the inert attribute
    document.getElementById('state').textContent = card.inert;
    toggle.textContent = card.inert ? 'Make it interactive again' : 'Make the card inert';
  });

  document.getElementById('save').addEventListener('click', () => {
    document.getElementById('n').textContent = ++clicks;
  });
</script>
</body>
</html>
Toggle inert on the card. While it is on, the Save button ignores clicks and Tab skips the card.

The typical use is the page behind a panel. While a drawer or a sheet is open, the content underneath should be off limits for the mouse, the keyboard and screen readers alike.

What inert switches off

inert applies to the element and everything inside it. The HTML spec defines what that means, and we checked the parts a script can measure.

pointer-events: none stops the mouse only. inert stops the mouse, the keyboard and focus.
pointer-events: none stops the mouse only. inert stops the mouse, the keyboard and focus.
  • Clicks do not reach the element. In our tests, the click event landed on the page body instead.
  • Tab skips every link, button and field inside it.
  • focus() from a script is refused, so nothing inside can become the active element.
  • Assistive technology is not given the inert content, according to the HTML spec. We did not test a screen reader for this page.

Nothing changes visually. Add your own style, such as opacity: .45, so a locked region looks locked.

pointer-events: none is not the same thing

pointer-events: none is often used to disable a section, but it only stops the mouse and touch. A keyboard user can still Tab to the buttons and press Enter. Try all three below.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>pointer-events vs inert vs interactivity</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; font-size: 14px; }
  p { margin: 0 0 10px; }
  .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; margin-top: 10px; }
  .box { background: #fff; border-radius: 10px; padding: 12px; box-shadow: 0 3px 10px rgba(0, 0, 0, .07); }
  .box b { display: block; margin-bottom: 8px; font-family: ui-monospace, Consolas, monospace; font-size: 13px; }
  button, input { font: inherit; padding: 7px 10px; border-radius: 8px; border: 1px solid #c9ced8; background: #fff; }
  .box input { width: 100%; box-sizing: border-box; margin-top: 8px; }
  :focus-visible { outline: 3px solid #f59e0b; outline-offset: 2px; }

  /* two of the three ways to block a region (the third is the inert attribute) */
  .no-pointer { pointer-events: none; }
  .css-inert { interactivity: inert; }

  #log { margin-top: 10px; padding: 8px 10px; background: #fff; border-radius: 8px; }
  #support { font-weight: 600; }
</style>
</head>
<body>
<p>Click each button, then press <b>Start here</b> and Tab through. <span id="support"></span></p>
<button id="start">Start here</button>

<div class="grid">
  <div class="box no-pointer">
    <b>pointer-events: none</b>
    <button data-name="pointer-events: none">Click me</button>
    <input placeholder="Type here" aria-label="Field A">
  </div>
  <div class="box" inert>
    <b>inert attribute</b>
    <button data-name="inert attribute">Click me</button>
    <input placeholder="Type here" aria-label="Field B">
  </div>
  <div class="box css-inert">
    <b>interactivity: inert</b>
    <button data-name="interactivity: inert">Click me</button>
    <input placeholder="Type here" aria-label="Field C">
  </div>
</div>

<div id="log">Nothing yet.</div>

<script>
  const log = document.getElementById('log');
  const ok = CSS.supports('interactivity', 'inert');
  document.getElementById('support').textContent =
    'This browser ' + (ok ? 'supports' : 'does not support') + ' interactivity: inert.';

  document.querySelectorAll('[data-name]').forEach((btn) => {
    btn.addEventListener('click', () => log.textContent = 'Clicked: ' + btn.dataset.name);
    btn.addEventListener('focus', () => log.textContent = 'Tab reached: ' + btn.dataset.name);
  });
</script>
</body>
</html>
Click each button, then Tab from Start here. Only the pointer-events box can still be reached with the keyboard.
pointer-events: none inert attribute interactivity: inert
Where you set it CSS HTML or JavaScript CSS
Blocks mouse and touch Yes Yes Yes
Blocks Tab and focus No Yes Yes
Worked in our Chromium 147 Yes Yes Yes
Worked in our Firefox 148 and WebKit 26.4 Yes Yes No, ignored

For clicks passing through an overlay to what is under it, CSS pointer events is the right tool. For keyboard order in general, see tabindex.

The attribute vs the CSS property

The attribute is a switch that a script flips. The property is a CSS declaration, so a selector can decide when it applies.

The attribute needs a script to toggle it. The property follows any selector. Support measured on 2026-09-26.
The attribute needs a script to toggle it. The property follows any selector. Support measured on 2026-09-26.

With the property, the page behind a drawer can be locked by one rule, with no script keeping it in sync:

body:has(.drawer.open) main {
  interactivity: inert;
  opacity: .5;
}

The :has() selector does the watching: when a drawer with the open class exists, main becomes inert. CSS :has() covers that selector.

Measured support

We ran the demos on this page in the Playwright builds of three engines on 2026-09-26: Chromium 147, Firefox 148 and WebKit 26.4. We did not test other browsers or versions.

  • The inert attribute blocked clicks, Tab and focus() in all three.
  • interactivity: inert worked in Chromium 147. Firefox 148 and WebKit 26.4 ignored the declaration, and CSS.supports returned false.

An engine that ignores the property leaves the region fully usable, so use it with a check and fall back to the attribute:

const cssInert = CSS.supports('interactivity', 'inert');
if (!cssInert) main.inert = open;

A <dialog> opened with showModal() makes the rest of the page inert without any extra code.

In all three engines, focus moved into the dialog, Tab never reached the page behind it, Esc closed it, and focus went back to the button that opened it (we opened it from the keyboard).

showModal gives you inertness, focus and Esc. A drawer or panel needs all four steps from you.
showModal gives you inertness, focus and Esc. A drawer or panel needs all four steps from you.

So you do not add inert around a modal dialog. You add it for everything the browser does not treat as modal: drawers, side panels, full-screen menus and overlays built from a div. HTML CSS modal compares the dialog element with a hand-built overlay.

Focus trapping, compared

Older focus traps listen for Tab and move focus back to the first or last control by hand. Making the background inert does the work instead: if nothing behind the panel can take focus, Tab has nowhere to go but the panel.

In our tests, pressing Tab past the last control in a modal dialog or an open drawer never landed on the page behind it. In Chromium and WebKit, focus left the page for the browser's own controls between rounds, then came back to the panel.

That exit is expected and lets keyboard users reach the address bar.

A finished example: dialog and drawer side by side

This page has both kinds. The dialog relies on showModal(). The drawer uses the CSS rule where it is supported and the attribute elsewhere; the grey line says which one ran.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Modal dialog vs drawer with an inert background</title>
<style>
  body { margin: 0; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; font-size: 15px; }
  main { padding: 16px; }
  h2 { margin: 0 0 6px; font-size: 18px; }
  .row { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0; }
  button, input { font: inherit; padding: 8px 12px; border-radius: 8px; border: 1px solid #c9ced8; background: #fff; cursor: pointer; }
  .primary { background: #1d4ed8; color: #fff; border-color: #1d4ed8; }
  :focus-visible { outline: 3px solid #f59e0b; outline-offset: 2px; }
  #mode { font-size: 13px; color: #4b5563; margin: 0; }

  dialog { border: 0; border-radius: 12px; padding: 18px; max-width: 280px; box-shadow: 0 20px 50px rgba(0, 0, 0, .3); }
  dialog::backdrop { background: rgba(17, 24, 39, .45); }

  .drawer {
    position: fixed; top: 0; right: 0; bottom: 0; width: min(280px, 85vw);
    padding: 18px; box-sizing: border-box; background: #fff;
    box-shadow: -10px 0 30px rgba(0, 0, 0, .2);
    transform: translateX(100%); visibility: hidden;
    transition: transform .25s, visibility 0s .25s;  /* hide only after sliding out */
  }
  .drawer.open { transform: none; visibility: visible; transition: transform .25s; }
  .drawer input { width: 100%; box-sizing: border-box; margin: 10px 0; }

  /* the whole trick: while the drawer is open, the page behind it is inert */
  body:has(.drawer.open) main { interactivity: inert; opacity: .5; }
</style>
</head>
<body>
<main>
  <h2>Order #1042</h2>
  <p id="mode"></p>
  <div class="row">
    <button class="primary" id="openDialog">Open modal dialog</button>
    <button class="primary" id="openDrawer">Open drawer</button>
  </div>
  <div class="row">
    <button id="bg">Background button (clicked <span id="n">0</span>)</button>
    <input placeholder="Background field" aria-label="Background field">
  </div>
</main>

<dialog id="dialog">
  <p>showModal() made the page behind inert for you. Press Esc, or:</p>
  <button id="closeDialog">Close</button>
</dialog>

<aside class="drawer" id="drawer" aria-label="Edit note">
  <b>Edit note</b>
  <input id="note" placeholder="Note for the courier" aria-label="Note">
  <button id="closeDrawer">Done</button>
</aside>

<script>
  const main = document.querySelector('main');
  const drawer = document.getElementById('drawer');
  const dialog = document.getElementById('dialog');
  const opener = document.getElementById('openDrawer');
  const cssInert = CSS.supports('interactivity', 'inert');
  document.getElementById('mode').textContent = 'The drawer locks the page with ' +
    (cssInert ? 'CSS interactivity: inert.' : 'the inert attribute (fallback).');

  // modal dialog: the browser handles inertness, focus and Esc
  document.getElementById('openDialog').addEventListener('click', () => dialog.showModal());
  document.getElementById('closeDialog').addEventListener('click', () => dialog.close());

  // drawer: we lock the background, move focus in, and give it back
  function setDrawer(open) {
    drawer.classList.toggle('open', open);
    if (!cssInert) main.inert = open;  // engines without the property: use the attribute
    if (open) document.getElementById('note').focus();
    else opener.focus();
  }
  opener.addEventListener('click', () => setDrawer(true));
  document.getElementById('closeDrawer').addEventListener('click', () => setDrawer(false));
  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape' && drawer.classList.contains('open')) setDrawer(false);
  });

  let n = 0;
  document.getElementById('bg').addEventListener('click', () => {
    document.getElementById('n').textContent = ++n;
  });
</script>
</body>
</html>
Open each one, then try the background button and Tab. Esc closes both.

The drawer code does four jobs:

  1. Lock the page: the body:has(.drawer.open) main rule, or main.inert = open as the fallback.
  2. Move focus in: call focus() on the note field when the drawer opens.
  3. Close on Esc: one keydown listener on the document.
  4. Give focus back: focus the Open drawer button when it closes.

The drawer also gets visibility: hidden while closed, so its field cannot be tabbed into while it is off screen.

When it does not work

What you see Cause Fix
Tab still reaches the locked buttons You used pointer-events: none Use inert or interactivity: inert
Works in Chrome, not in Firefox or Safari interactivity is ignored there Fall back to the inert attribute
The panel itself cannot be used The panel is inside the inert element Move it outside, next to main
A child set to interactivity: auto is still locked The inert parent wins Move that child out of the region
The region looks normal but ignores clicks inert has no visual effect Add your own style, such as opacity
Focus is lost after closing the drawer Nothing refocuses the opener Call focus() on the opening button
Tab reaches a closed drawer's field The drawer is only moved off screen Add visibility: hidden while closed

Whether a lock works is something people need to try with a mouse and a keyboard; a screenshot cannot show it. To send the working version, paste the page into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written and its scripts run, so the people you send it to can open the drawer and press Tab themselves. If you change the code later, the same link shows the new version.

Questions people ask

What is the difference between inert and interactivity: inert?

They switch off the same things. inert is an HTML attribute you add or remove, usually from JavaScript. interactivity: inert is a CSS property, so any selector, class or media query can turn it on. In our tests, the attribute worked in Chromium, Firefox and WebKit, and the property worked only in Chromium.

Is pointer-events: none enough to disable a section?

No. It stops mouse and touch clicks only. The buttons inside can still be reached with Tab and pressed with Enter, and a script can still focus them. Use inert when the section must not be used at all.

Do I need inert when I use dialog.showModal()?

No. showModal() makes everything outside the dialog inert by itself, moves focus into the dialog and closes it on Esc. You need inert, or interactivity: inert, for things the browser does not treat as modal, such as a drawer, a side panel or a hand-built overlay.

Can an element inside an inert region opt back in?

Not with the inert attribute. In Chromium 147 we also set interactivity: auto on a child of an interactivity: inert parent: the computed value read auto, but the child still could not be focused or clicked. Put the interactive part outside the inert region instead.

Does inert change how the element looks?

No. The content is painted exactly as before. Add your own style, such as lower opacity, so people can see that the region is locked.

Keep reading