8000 Make the OTP device picker in the keycloak.v2 login theme keyboard accessible by Rishavbhattarai · Pull Request #54079 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

Make the OTP device picker in the keycloak.v2 login theme keyboard accessible - #54079

Open
Rishavbhattarai wants to merge 3 commits into
keycloak:mainfrom
Rishavbhattarai:issue-51208-otp-picker-keyboard
Open

Rishavbhattarai wants to merge 3 commits into
keycloak:mainfrom
Rishavbhattarai:issue-51208-otp-picker-keyboard

Conversation

@Rishavbhattarai
@Rishavbhattarai Rishavbhattarai commented Oct 17, 2026 •
Copy link
Copy Markdown

Closes #51208

The OTP device picker in the keycloak.v2 login theme rendered each credential as a <div onclick>. Keyboard users couldn't reach it or change the selected device, screen readers got no role or state, and it did nothing with JavaScript disabled.

Changes:

  • login-otp.ftl: each credential is now an <input type="radio" name="selectedCredentialId"> with the PatternFly tile as its <label>, the same structure as the legacy keycloak theme. The group has role="radiogroup" and an accessible name from a new message, loginOtpCredentialSelection. The inline script is gone.
  • The radio is visually hidden (kcLoginOTPListInputClass=pf-v5-screen-reader). styles.css styles the tile from input:checked + label with PatternFly's own selected-tile values, and draws a focus outline while the radio has keyboard focus. The default credential shows as selected on page load (before this, no tile was highlighted), and selecting works the same with JavaScript disabled.
  • keycloak.v2 inherits login-reset-otp.ftl from base, which uses the same input class, so the reset picker gets the same hidden radios and checked styling.
  • kcLoginOTPListSelectedClass is removed from the v2 theme.properties because nothing uses it anymore.
  • With a single credential, the hidden selectedCredentialId input is unchanged.

Tests:

  • New BrowserFlowTest#testUserWithTwoAdditionalFactorsSelectedWithKeyboard: checks the default device is pre-selected, presses Shift+Tab from the one-time code field and asserts that focus lands on the checked radio, then presses the down arrow to select the second device and logs in with its code. With the radios taken out of the tab order, it fails (focus lands on a button). It's skipped on HtmlUnit, which doesn't move focus or selection with these keys, so it runs in the Chrome and Firefox Forms IT jobs.
  • testUserWithTwoAdditionalFactors now also asserts the default device is pre-selected, so HtmlUnit covers that part.
  • Both LoginTotpPage page objects look up label tiles instead of div tiles and read the selection from the checked radio. ResetOtpPage.selectOtp clicks the tile instead of the hidden radio.

Locally: BrowserFlowTest 30/30 and ResetOtpTe 10000 st 4/4 in headless Chrome; on HtmlUnit BrowserFlowTest 29/30 with 1 skipped and ResetOtpTest 4/4. MultiFactorAuthenticationTest passes.

I used Claude Code (an AI agent) to help write the change, the tests and this description. I reviewed every line and can explain and revise it.

Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Visible selection is lost in the inherited OTP reset picker and becomes stale during login with JavaScript disabled.

2 open issues
What changed in this PR

Improves keyboard and screen-reader access to the keycloak.v2 OTP device picker, addressing #51208.

Changes:

  • Replaces clickable tiles with labeled native radios and an accessible group name.
  • Adds default selection styling and visible keyboard focus.
  • Updates page objects and adds keyboard-selection coverage.
File Description
themes/​src/​main/​resources/​theme/​keycloak.v2/​login/​theme.properties Adds visually hidden radio styling.
themes/​src/​main/​resources/​theme/​keycloak.v2/​login/​resources/​css/​styles.css Outlines the keyboard-focused tile.
themes/​src/​main/​resources/​theme/​keycloak.v2/​login/​login-otp.ftl Adds radios, labels, and selection synchronization.
themes/​src/​main/​resources/​theme/​base/​login/​messages/​messages_en.properties Names the device-selection group.
testsuite/​integration-arquillian/​tests/​base/​src/​test/​java/​org/​keycloak/​testsuite/​forms/​BrowserFlowTest.java Tests default and keyboard selection.
testsuite/​integration-arquillian/​tests/​base/​src/​main/​java/​org/​keycloak/​testsuite/​pages/​LoginTotpPage.java Updates tile lookup and adds keyboard interaction.
test-framework/​ui/​src/​main/​java/​org/​keycloak/​testframework/​ui/​page/​LoginTotpPage.java Updates tile lookup to labels.

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 19, 2026 06:18
@Rishavbhattarai
Rishavbhattarai force-pushed the issue-51208-otp-picker-keyboard branch from 3d3968a to 3890c39 Compare October 19, 2026 06:18
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The keyboard regression test bypasses tab-order navigation, leaving the reported reachability failure untested.

2 open issues
1 resolved since last review

Review effort: Balanced

@Rishavbhattarai
Rishavbhattarai force-pushed the issue-51208-otp-picker-keyboard branch from 3890c39 to 8597860 Compare October 19, 2026 06:24
Copilot AI balanced review requested due to automatic review settings October 19, 2026 06:31
@Rishavbhattarai
Rishavbhattarai force-pushed the issue-51208-otp-picker-keyboard branch from 8597860 to 6f49dc6 Compare October 19, 2026 06:31
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The focused changes address the accessibility issue with no unresolved defects identified; tests were not executed during this review.

No open issues 🎉

2 resolved since last review

Review effort: Balanced

@rmartinc
rmartinc force-pushed the issue-51208-otp-picker-keyboard branch from 6f49dc6 to ec7217f Compare November 5, 2026 15:09
Copilot AI balanced review requested due to automatic review settings November 5, 2026 15:10
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

No active review issues are recorded for the OTP device-picker accessibility changes.

No open issues 🎉

Review effort: Balanced

@rmartinc rmartinc left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @Rishavbhattarai! LGTM in general! Just one comment, WDYT about using a class and the theme properties instead of assigning directly to the name? Do you think it makes sense?

@rmartinc
rmartinc commented Nov 5, 2026
Copy link
Copy Markdown
Contributor

@mabartos Do you want to take a look to this one too?

@mabartos
mabartos self-requested a review November 5, 2026 15:56
Copilot AI balanced review requested due to automatic review settings November 6, 2026 04:49
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

There are no active review issues for the OTP device picker accessibility changes.

No open issues 🎉

Review effort: Balanced

@rmartinc rmartinc left a comment •
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @Rishavbhattarai for the changes! Another nit-pick that I realized, maybe putting the css to the label instead of the class is also better (because the class is also in a property). I have not tested it, so please check that I'm not doing something wrong.

And please rebase your branch to pick up the last changes because there was a CI error in JS that was fixed yesterday.

For me with this, the PR is OK if the tests pass. Thanks again!

} No newline at end of file
}
/* OTP device pickers (login and reset): the radio is visually hidden, so the tile shows its checked state and keyboard focus */
.kc-login-otp-radio:checked + .pf-v5-c-tile {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
.kc-login-otp-radio:checked + .pf-v5-c-tile {
.kc-login-otp-radio:checked + label {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point, applied both in f989536. I checked in a browser that the checked tile and the keyboard focus outline still show. I'll rebase on main next to pick up the JS CI fix. Thanks for the reviews!

--pf-v5-c-tile--after--ScaleY: var(--pf-v5-c-tile--m-selected--after--ScaleY);
}

.kc-login-otp-radio:focus-visible + .pf-v5-c-tile {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
.kc-login-otp-radio:focus-visible + .pf-v5-c-tile {
.kc-login-otp-radio:focus-visible + label {

Rishavbhattarai and others added 3 commits November 6, 2026 10:02
…cessible

- render each OTP credential as a radio input with the tile as its label, like the legacy keycloak theme
- give the group an accessible name and a visible keyboard focus outline
- style the selected tile from the checked radio, so the default credential is shown as selected on page load and the selection works without JavaScript
- apply the same styling to the inherited reset OTP picker, whose radios are now hidden too

Closes keycloak#51208

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP6NzY6hpzUKArjJCC4Mv1
Signed-off-by: Rishavbhattarai <bhattarai.rishav@gmail.com>
Select the visually hidden radios by the kc-login-otp-radio class, set
through kcLoginOTPListInputClass, instead of by the input name.

Closes keycloak#51208

Signed-off-by: Rishavbhattarai <bhattarai.rishav@gmail.com>
Closes keycloak#51208

Signed-off-by: Rishavbhattarai <bhattarai.rishav@gmail.com>
Copilot AI balanced review requested due to automatic review settings November 6, 2026 18:02
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

No active review issues remain for the OTP device picker accessibility changes.

No open issues 🎉

Review effort: Balanced

@Rishavbhattarai
Rishavbhattarai force-pushed the issue-51208-otp-picker-keyboard branch from f989536 to 9dbfeda Compare November 6, 2026 19:19
Copilot AI balanced review requested due to automatic review settings November 6, 2026 19:19
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

No active review issues were identified for the OTP device picker accessibility changes.

No open issues 🎉

Review effort: Balanced

@rmartinc rmartinc left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks a lot @Rishavbhattarai! But I saw that there was another bigger PR that was pending approval in #52724 (bigger refactor to move keycloak.v2 to base). I saw that one also fixed this issue indirectly and using another approach (that one changed the JS). I rather prefer this one as we are getting rid of any JS and using plain html. @mabartos WDYT? Do we continue with this but now in the moved files? @Rishavbhattarai wait a bit until Martin comments about this, I don't want to make you work again for nothing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

login/ui: OTP device picker in keycloak.v2 is not reachable or operable by keyboard

4 participants

0