The inert attribute

One HTML attribute that makes a whole subtree unfocusable, unclickable, and invisible to assistive technology. It is the cleanest way to lock the steps a user hasn't reached yet.

One line in your framework

Every framework binds it the same way: a boolean that adds or removes the attribute. Pick yours:

function Step({ current, children }) {
  return (
    <section inert={!current}>
      {children}
    </section>
  );
}

On React 18 or older, inert isn't a known prop, so set the property directly with a tiny hook:

function useInert(active) {
  const ref = useRef(null);
  useEffect(() => {
    if (ref.current) ref.current.inert = active;
  }, [active]);
  return ref;
}
<script setup>
const props = defineProps({ isCurrentStep: Boolean });
</script>

<template>
  <section :inert="!isCurrentStep">
    <slot />
  </section>
</template>
<script>
  let { current, children } = $props();
</script>

<section inert={!current || undefined}>
  {@render children()}
</section>
<!-- step.component.html -->
<section [inert]="!isCurrentStep">
  <ng-content />
</section>
function Step(props) {
  return (
    <section inert={!props.current}>
      {props.children}
    </section>
  );
}
render() {
  return html`
    <section ?inert=${!this.currentStep}>
      <slot></slot>
    </section>
  `;
}
<section x-data="{ isCurrentStep: false }"
         :inert="!isCurrentStep">
  <!-- your step content -->
</section>

That's the whole pattern: one line per step — instead of disabled on every control plus aria-hidden on the section plus hoping you caught everything.

Live demo: a three-step flow

  1. Press Tab through the form below with inert off. Watch focus wander into steps 2 and 3 before you've reached them.
  2. Check "Apply inert to upcoming steps" and tab again. The future steps leave the keyboard's world entirely.
  3. Complete the steps normally — the toggle works mid-flow too.

Step 1 — Account

Tell us who you are.

Step 2 — Preferences

How should we reach you?

Step 3 — Review

Does this look right?

Name
—
Email
—
Notifications
—

Focus log

Focus events will appear here. Press Tab inside the form.

What to notice

Without inert: the upcoming steps render dimmed, but dimming is just paint. Tab walks straight into step 2's checkboxes and step 3's buttons before they've been "reached." Screen readers see them too. The red entries in the log are focus escaping the current step.

With inert: future steps drop out of the tab order and the accessibility tree in one attribute. No per-control disabled, no aria-hidden juggling, and you can't forget one — the whole subtree is covered.

It composes with everything else: keep your visual "locked" styling, keep the step indicator, keep validation. inert only owns the interaction contract. And when a step becomes current, removing the attribute restores everything exactly as it was.