Accessible progressive UI
Finish and test the board
Run the final interface through keyboard, screen reader, JavaScript-off, slow-network, invalid-state, and replacement scenarios.
The board is built. Server-rendered issues, a filter panel, expandable rows, an inline editor, URL and storage sync, HTMX swaps. Now we find out whether it’s progressively enhanced or only looks that way.
Progressive enhancement means the core task works with nothing, and every layer you add helps without becoming required. You prove it by taking layers away.
The core task with nothing
Disable JavaScript in your browser and reload the board. You should still read every issue, submit the filter form, and reach an issue’s edit page from a plain link. Slower, uglier, complete.
If any of that fails, an enhancement became a dependency. The usual suspect is a <button type="button"> doing a <form>’s job.
The test matrix
Write the matrix before testing, so you can’t skip the rows you expect to fail:
| Scenario | What must be true |
|---|---|
| Keyboard only | Tab reaches every control, Escape closes the editor, focus never lost |
| Screen reader | Buttons announce expanded/collapsed, errors read aloud |
| JavaScript off | Read, filter, and edit through full page loads |
| Slow 3G | “Saving…” shown, button disabled, one request |
| Server 422 | Error visible and announced, form still editable |
| Offline | Same as 422, network error message |
| Reduced motion | No transitions, focus lands instantly |
| Corrupt storage | Bad issue-filter value falls back to defaults |
| List replaced | Filter panel keeps state, new rows initialize, focus preserved |
Run every row and write the result next to it. A screenshot beats a checkmark.
Where the board will fail
The rows that usually fail first:
The list swap drops focus. The Save button sits inside the swapped <ul>, so after saving, focus falls to <body>. Fix it with an @htmx:after-swap handler that focuses the row’s heading.
The offline row shows “Failed to fetch”. Correct, but useless to a person. Map it to “Couldn’t reach the server. Try again.”
Reduced motion still animates the panel. That’s the x-transition modifier form setting inline styles. Class-based transitions let the media query turn them off.
Keep one honest limitation
Every board has something you decided not to fix. Here: an editor left open when the list is swapped is discarded, and the typed text is lost. Fine for a first version. Write it in the project notes instead of hiding it behind a spinner.
Finish your own board now. Fill in every row with evidence, write down your one limitation, and hand the page to someone who has never seen it. If they can filter, expand, and edit an issue with no explanation, you’re done.
Lesson completed