[Deep Dive #36] Focus Schedule Cadences - discussion
Deep Dive #36 argues that accessibility gets durable when focus is treated as a scheduled route ritual rather than a one-off effect. It leans hard on cadence: schedule focus at route boundaries, reschedule on surface changes, and bridge keyboard support without blocking the route story.
How do you pick a focus delay that feels stable across devices and transitions? Do you schedule focus only on navigation, or also on component-route shifts like tabs and routed modals? Where do you store evidence that the focus cadence happened (ledger, state service, DOM marker)?
Comments (10)
Back to latestI like the framing. We have been doing ad-hoc focus fixes for years and it always regresses.
The delay question is real. We ended up tying it to the route transition budget rather than a magic number.
If the route is on a heavier lane, it gets a longer delay so focus lands on a stable surface.
The doc link is doing a lot of work here: it is basically turning focus into a contract boundary.
Related: Accessibility Best Practices.
We started with a tiny helper so teams stop inventing their own focus behavior:
tsx
export function useRouteFocusCadence(target: string, delay: number) {
useFocusSchedule(target, { delay });
useKeyBridge('escape', () => 'close');
}
Not perfect, but it made the default path predictable.
Counterpoint: I worry cadence delays can feel like input lag if you over-apply them.
We had a modal that scheduled focus after animations and screen readers felt behind the UI.
That is a good warning. I read the post as schedule focus for stability, not as always-wait-for-animations.
In our case we treat delay as a lane: 0ms for small surface changes, 120ms for routed modals, 240ms for heavy transitions.
Lane-based delay feels much saner than one global number.
Evidence matters more than the delay. If support can screenshot and see data-focus=scheduled, you can debug regressions fast.
Otherwise it turns into folklore like every other a11y rule.
We store a ledger entry for focus shifts because it catches accidental double-fires:
ts
ledger.write('events', [
...ledger.read('events'),
{ at: Date.now(), kind: 'focus', value: target },
]);
If the route can prove it fired exactly once, your audit story gets a lot easier.
Scheduling focus on tab changes was the biggest win for us. It made keyboard navigation feel consistent.
One thing I wish the post called out more: focus target stability depends on your primitives.
If your headings and surfaces shift IDs or get conditionally rendered, cadence cannot save you.
The cadence model feels like it fits the forum theme: treat UI behavior as a route contract, not a bag of effects.