Repository navigation
fix: give the JWT decoder switch real tab semantics and name its fields - #207
Conversation
The Decode/Encode switch looked like tabs but was two plain buttons, so screen reader users had no tab semantics and no arrow-key navigation. Follow the Base64 tool: a tablist with aria-label, role=tab on each button with aria-selected, aria-controls and a roving tabIndex, Arrow/Home/End handling with wraparound, and role=tabpanel with aria-labelledby on each panel. Also name the fields that had none: the token textarea, the Endpoint input (now tied to its label with htmlFor and id), the payload editor, the secret/key inputs and the JWKS URL. The decoding error block becomes role=alert, and the verification toggle gets aria-expanded and aria-controls. No className changes, so both themes look identical.
anurag629
left a comment
There was a problem hiding this comment.
Thanks @varun-pahuja, this is close. I checked it in Chrome on npm run dev: only the active tab is in the tab order (Tab from Decode goes to Load example), the arrow keys wrap, Home and End work, the panel follows with aria-labelledby, the token field is named "Encoded JWT", and an invalid token shows the failure block as an alert. No class changed, and lint, all 360 tests, the build and the page check pass on top of develop.
One thing: the "How verification works" toggle has aria-controls="jwt-verification-explained", but nothing has that id, even when it's open. Please put id="jwt-verification-explained" on the explanation <div> at line 523, the one that shows when open is true.
The toggle got aria-controls="jwt-verification-explained" but nothing carried that\nid, so the control referenced nothing. Give the explanation block the id it\nis pointed at.
|
@anurag629 Fixed in
|
anurag629
left a comment
There was a problem hiding this comment.
Thanks @varun-pahuja, the toggle points at the block now, and in Chrome the element exists once it's expanded. Lint, all 360 tests, the build and the page check still pass on top of develop.
What and why
The JWT decoder's Decode/Encode switch looked like tabs but was two plain buttons, and several of its fields had no accessible name, so a screen reader announced "edit text, blank".
This follows the pattern already used by the Base64 tool (
Base64Client.tsx: the key handler and the tablist and tabpanel):role="tablist"with anaria-label, each button withrole="tab",aria-selected,aria-controlsand a rovingtabIndex, Left/Right/Home/End handling with wraparound, androle="tabpanel"witharia-labelledbyon each panel.htmlFor/id, and the payload editor, the secret and key inputs and the JWKS URL input now have names.role="alert", and the verification toggle hasaria-expandedandaria-controls.No
classNamevalue changes anywhere, so both themes look exactly as before.Closes #90
How I tested this
npm run lint(0 errors, 13 warnings, the same asdevelop),npm run test(17 files, 360 tests pass) andnpm run build, Node v22.23.3./jwt-decoderwithnpm run dev:/jwt-decoderthe served HTML has tworole="tab"buttons witharia-selected="true"/"false", exactly onetabindex="0"and onetabindex="-1", arole="tablist"witharia-label="JWT decoder mode", arole="tabpanel"witharia-labelledby, andaria-label="Encoded JWT"on the token textarea. That singletabindex="0"is the mechanical reason Tab skips Encode.aria-labelon the payload editor, the HMAC secret, the private key or JWK, the shared HMAC secret, the public key or JWK and the JWKS URL, plusid="jwt-curl-endpoint"on the Endpoint input,role="alert"on the failure block, andaria-expandedplusaria-controlson the toggle.AI assistance
I used space-bunny-free, an OpenCode-hosted model, for these three file edits and for running the commands above, under my direction. I ran the keyboard walkthrough and inspected the rendered DOM myself, and this description is written from my notes.
Type of change
Checklist
npm run lint && npm run test && npm run buildpasses locallylib/and have a test inlib/__tests__/page.tsx, client component,opengraph-image.tsx, and the bug report dropdown are all doneNotes for reviewers
Two things the issue does not spell out, both confirmed with @anurag629 before opening:
const TAB_IDS: TabId[] = ["decode", "encode"];and atablistRef. The arrow key arithmetic needs the count and order, andtablistRefis how the handler finds the sibling button to focus. This matches whatBase64Client.tsxalready does with its ownTABSarray andtablistRef, so the two tools read the same way.I also rebased onto the current
develop(it had moved by 11 commits, one of which touched this file), and the gates above were re-run after the rebase.