DEV Community

Cover image for I Fixed a Modal That Said “Accessible” But Wasn’t
Pawan Satoshi
Pawan Satoshi

Posted on

I Fixed a Modal That Said “Accessible” But Wasn’t

Summer Bug Smash: Clear the Lineup 🐛🛹

This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry.

Project Overview

The Comfort Table is an interactive comfort-food editorial experience originally built for the DEV Frontend Challenge.

Visitors can discover comfort dishes through mood and moment, explore a global menu, and open detailed dish stories inside a custom modal.

The project uses semantic HTML, responsive CSS, vanilla JavaScript, visible focus states, aria-pressed, aria-live, reduced-motion support, and touch-friendly interactions.

Bug Fix or Performance Improvement

The bug was in the custom dish-details modal.

The component declared itself with role="dialog" and aria-modal="true", but its behavior did not fully satisfy what those semantics promised.

The visual modal worked, but the focus-management lifecycle was incomplete.

A robust modal needs to:

  • move focus into the dialog when it opens
  • keep Tab and Shift+Tab inside the dialog
  • close with Escape
  • restore focus to the element that opened it
  • prevent interaction with the background while open

The root problem was treating ARIA semantics and modal behavior as separate concerns.

A dialog should not claim to be modal while still allowing its interaction model to behave like a normal page.

Code

The accessibility fix is documented in this commit:

62a7f04 — fix: harden modal accessibility

Repository:

github.com/pawansatoshi/the-comfort-table

Primary implementation:

index.html

The fixed modal lifecycle is:

Invoking button → Open modal → Move focus inside → Contain Tab / Shift+Tab → Escape or close → Restore focus → Return to invoking button

The implementation also makes the background inert while the dialog is active and keeps overlay and close behavior within the same lifecycle.

My Improvements

I approached the bug as an accessibility behavior problem rather than simply adding ARIA attributes.

1. Initial Focus

When the modal opens, focus is moved into the dialog instead of leaving the user somewhere behind the modal.

2. Focus Containment

Tab and Shift+Tab are handled so keyboard focus remains within the modal boundary.

3. Escape Handling

The dialog can be dismissed with Escape, providing a predictable keyboard exit.

4. Focus Restoration

When the modal closes, focus returns to the dish control that originally opened it.

This prevents keyboard users from losing their position in the page.

5. Background Inertness

The page behind the modal is treated as unavailable while the dialog is open.

This makes the implementation consistent with the declared aria-modal="true" behavior.

6. Regression Safety

The fix was designed around the existing UI rather than replacing the component with a new visual implementation.

The responsive layout, dish experience, and existing interaction design remain intact.

The key engineering principle was:

ARIA should describe real behavior, not replace it.

Verification

I manually verified the deployed application on mobile.

The following behaviors were checked:

  • modal opens from the dish details control
  • modal closes correctly
  • overlay interaction behaves correctly
  • repeated open and close cycles remain stable
  • refresh does not leave the interface in a broken modal state
  • responsive layout remains usable

The implementation also contains the keyboard lifecycle for Tab, Shift+Tab, Escape, and focus restoration.

I am deliberately not claiming an independently performed desktop keyboard pass because this submission was prepared from a mobile device.

Impact

This was a focused code fix with a meaningful accessibility impact.

Without proper focus management, a custom modal can be visually correct while still creating a confusing experience for keyboard and assistive-technology users.

The fix brings the component's actual interaction model closer to the semantics it already declared.

It also establishes a reusable pattern for future dialogs in the project:

semantic state + focus management + keyboard behavior + background isolation + focus restoration

Live Demo

https://the-comfort-table.vercel.app/

Repository

https://github.com/pawansatoshi/the-comfort-table

Accessibility Fix Commit

https://github.com/pawansatoshi/the-comfort-table/commit/62a7f04a88d798c0dc6221f7c647b344f84f221b

Supporting Documentation

https://github.com/pawansatoshi/the-comfort-table/blob/main/DEV-SUBMISSION.md

https://github.com/pawansatoshi/the-comfort-table/blob/main/README.md

bugsmash

Top comments (0)