Fixing Focus Traps & Auto-Open Surprises in the Wear & Tear Slide-Over
Fixing Focus Traps & Auto-Open Surprises in the Wear & Tear Slide-Over
Release v0.1.105 · Accessibility · Deduction Claim Builder
Background
The Deduction Claim Builder lets landlords and agents itemise deposit deductions category by category. Alongside the main AddDeductionDialog, a Wear & Tear (W&T) Guide slide-over panel provides contextual guidance on what counts as fair wear and tear versus chargeable damage.
In practice, two bugs made this flow difficult — and in some cases unusable — for people relying on keyboard navigation or screen readers.
The Problems
1. Focus was never moved into the slide-over
AddDeductionDialog renders with a z-50 backdrop. WearTearSlideOver was rendered outside that backdrop (noted in the code comments) and used its own z-40/z-50 stack. When the slide-over opened, the browser's focus remained inside the dialog behind it. A screen reader user received no announcement that anything had changed, and keyboard focus stayed trapped in the wrong layer.
2. The slide-over opened without being asked
Whenever a user selected a deduction category for a new deduction, the application automatically opened the W&T Guide:
// Previous behaviour — triggered on every category selection
if (cat && !editDeduction) {
setSlideOverOpen(true)
}
This auto-open had two downsides:
- It interrupted the user's flow with content they may not have wanted.
- It moved the visual focus point unpredictably, compounding the focus-trap problem described above.
What We Fixed
Auto-open removed
The conditional setSlideOverOpen(true) call on category selection has been removed. The W&T Guide now opens only when the user explicitly clicks the W&T Guide button. The principle of least surprise is restored — the UI does what the user asks, nothing more.
Proper ARIA semantics
WearTearSlideOver now declares itself correctly to assistive technology:
<div role="dialog" aria-modal="true" aria-label="Wear & Tear Guide">
<!-- slide-over content -->
</div>
aria-modal="true" signals to screen readers that content outside the slide-over should be treated as inert while it is open.
Focus management on open
When the slide-over opens, focus is programmatically moved to the first interactive element inside it (typically the close button or the first navigable section heading). A self-contained focus trap keeps keyboard navigation within the panel until it is dismissed.
Live region announcement
An aria-live="polite" region fires when the slide-over opens, giving screen reader users an explicit cue:
<div aria-live="polite" aria-atomic="true">
{slideOverOpen ? 'Wear & Tear Guide opened.' : ''}
</div>
Escape key scoped correctly
Previously, pressing Esc could close the parent AddDeductionDialog rather than the slide-over in front of it. The key handler is now scoped so that Esc closes only the slide-over — the dialog behind it remains open and in its current state.
Impact
| User group | Before | After |
|---|---|---|
| Keyboard-only users | Focus trapped behind slide-over; could not navigate guide content | Focus moves into slide-over; full keyboard nav available |
| Screen reader users | No announcement on slide-over open; no modal boundary | aria-live announcement fires; aria-modal marks boundary |
| All users | Slide-over opened automatically on category pick | Slide-over opens only on explicit button click |
Affected Component
src/app/dashboard/deductions/[tenancyId]/deduction-claim-builder.tsx
No changes to data models, API routes, or stored deduction records.