inert attributeOne 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.
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.
Tell us who you are.
How should we reach you?
Does this look right?
Three steps, no focus escapes.
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.