Skip to content

fix: give the JWT decoder switch real tab semantics and name its fields - #207

Merged
anurag629 merged 2 commits into
codercops:developfrom
varun-pahuja:fix/90-give-the-jwt-decoder-s-decode-encode-swi
Oct 20, 2026
Merged

anurag629 merged 2 commits into
codercops:developfrom
varun-pahuja:fix/90-give-the-jwt-decoder-s-decode-encode-swi

Conversation

@varun-pahuja

@varun-pahuja varun-pahuja commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

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):

  • The switch is a real tab strip: role="tablist" with an aria-label, each button with role="tab", aria-selected, aria-controls and a roving tabIndex, Left/Right/Home/End handling with wraparound, and role="tabpanel" with aria-labelledby on each panel.
  • The token textarea is named, the Endpoint label is tied to its input with htmlFor/id, and the payload editor, the secret and key inputs and the JWKS URL input now have names.
  • The decoding failure block is role="alert", and the verification toggle has aria-expanded and aria-controls.

No className value changes anywhere, so both themes look exactly as before.

Closes #90

How I tested this

  • npm run lint (0 errors, 13 warnings, the same as develop), npm run test (17 files, 360 tests pass) and npm run build, Node v22.23.3.
  • Keyboard walkthrough, in Chrome on /jwt-decoder with npm run dev:
    • Tabbing reaches Decode, and the next Tab skips Encode and moves to "Load example", so only the active tab is in the tab order.
    • ArrowRight and ArrowLeft move between the two tabs and the panel follows, ArrowRight twice wraps back around to Decode rather than dead-ending, and Home and End select the first and last tab.
    • Pasting an invalid token announces the decoding failure straight away, without moving focus.
    • No visual difference against the Base64 tool.
  • I did not walk every field with a screen reader, so I checked the rendered DOM instead. On /jwt-decoder the served HTML has two role="tab" buttons with aria-selected="true"/"false", exactly one tabindex="0" and one tabindex="-1", a role="tablist" with aria-label="JWT decoder mode", a role="tabpanel" with aria-labelledby, and aria-label="Encoded JWT" on the token textarea. That single tabindex="0" is the mechanical reason Tab skips Encode.
  • In the build output the remaining names are all present: aria-label on the payload editor, the HMAC secret, the private key or JWK, the shared HMAC secret, the public key or JWK and the JWKS URL, plus id="jwt-curl-endpoint" on the Endpoint input, role="alert" on the failure block, and aria-expanded plus aria-controls on 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

  • Bug fix
  • New tool or feature
  • Tests
  • Refactor or cleanup
  • Docs

Checklist

  • I was assigned the linked issue
  • npm run lint && npm run test && npm run build passes locally
  • Logic changes live in lib/ and have a test in lib/__tests__/
  • For a bug fix: the new test fails without my change
  • No analytics, trackers, or calls that send user data off-device
  • Works in both light and dark themes (if UI changed)
  • Inputs have labels and it works with the keyboard alone (if UI changed)
  • New tool: registry entry, page.tsx, client component, opengraph-image.tsx, and the bug report dropdown are all done

Notes for reviewers

Two things the issue does not spell out, both confirmed with @anurag629 before opening:

  • A module level const TAB_IDS: TabId[] = ["decode", "encode"]; and a tablistRef. The arrow key arithmetic needs the count and order, and tablistRef is how the handler finds the sibling button to focus. This matches what Base64Client.tsx already does with its own TABS array and tablistRef, so the two tools read the same way.
  • Where adding an attribute pushed a line past the width, the element is wrapped onto several lines. Prettier does not require it here and the original file is not Prettier clean, so it is a formatting choice. Lines that the change does not touch are left as they are.

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.

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 anurag629 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@varun-pahuja

varun-pahuja commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor Author

@anurag629 Fixed in 204850b. The explanation block now carries id="jwt-verification-explained", so the toggle's aria-controls points at the element it expands. The id is only in the DOM while the block is open, which is when the control is expanded.

npm run lint (0 errors, 13 warnings), npm run test (17 files, 360 tests) and npm run build all pass.

@anurag629 anurag629 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@anurag629 anurag629 added the hacktoberfest-accepted PR counts for Hacktoberfest label Oct 20, 2026
@anurag629
anurag629 merged commit cf96694 into codercops:develop Oct 20, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hacktoberfest-accepted PR counts for Hacktoberfest

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Give the JWT decoder's Decode/Encode switch tab semantics and label its inputs

2 participants