Ory Kratos recovery flow ↔ Screen reader compatibility: What adjustments ensure keyboard navigation consistency across identity providers?
0 reputation · 23 Sept 2023, 07:10 UTC
0 reputation · 23 Sept 2023, 07:10 UTC
The Ory Kratos self‑service recovery flow is intended to allow users to reset passwords or recover accounts, but the default HTML templates provide minimal accessibility support. Keyboard navigation through form fields, links, and buttons may be inconsistent, especially when different identity providers are chained into the same recovery sequence.
Customizing the recovery templates is the only way to add semantic HTML and ARIA attributes, yet it is unclear how to guarantee that these adjustments will work uniformly across all supported identity providers. The interplay between provider‑specific templates and the core recovery flow can introduce focus order regressions and screen‑reader confusion.
To move forward, we need to understand the exact constraints and best‑practice patterns for this integration.
35275 reputation · 23 Sept 2023, 12:34 UTC
Cross-provider consistency is not achieved by tuning anything per identity provider. In stock Kratos (version assumption: v1.x self-service flows — verify against your pinned release), every recovery flow is rendered through a single template or a single ui.nodes payload; the provider or recovery method only changes which nodes appear in that payload. The work is therefore to make your one rendering layer provider-agnostic: render nodes in payload order with native HTML, wire ARIA from node metadata generically, and verify each provider-initiated path. You cannot guarantee Tab order by hardcoding fields — you get it from render order plus per-flow checks.
One premise to check first: unmodified Kratos recovery is identifier-based (email plus a code or link); provider chaining such as OIDC happens in login and registration. If provider buttons appear on your recovery page, they come from customized nodes or templates — and injected nodes are exactly where Tab-order regressions originate. Exact node composition varies by version and configuration, so treat this paragraph as marked for review.
Well-established web behavior, independent of Kratos version:
tabindex breaks it. Use none at all.input, button, and a href elements are keyboard-operable without script.id referenced via aria-describedby.Kratos-specific points, stated as assumptions to verify: hidden fields (csrf_token, flow id) arrive as nodes in the payload; Kratos enforces no accessibility itself; overridden templates are not migrated for you on upgrade, so node changes can silently alter order.
ui.nodes in delivered order, hidden fields included, where the payload places them. Never maintain a hardcoded field list; a new provider or method will shift nodes and change Tab order silently.<label for>, messages to aria-describedby, autocomplete="email" on the identifier field, and autocomplete="one-time-code" if you use code-based recovery.aria-labelledby on the form pointing at a visible heading, and keep a persistent error container with role="alert". On full page loads, live regions are not reliably announced, so also move focus to the first invalid field or an error summary after a failed submit.<nav aria-label="Recovery">, with unique accessible names.tabindex="-1" or the first field — and test with password managers, which disagree about autofocus after redirects.Start a recovery flow through each configured method, then Tab from page load and confirm the order: identifier field, submit, secondary links. Run axe-core against each rendered flow URL and do one manual pass with NVDA or VoiceOver, since automated tools miss announcement timing. A scoped check:
npx @axe-core/cli "http://localhost:4433/self-service/recovery?flow=<id>"
Adjust host, port, and flow id to your deployment.
Are you overriding the server-rendered templates, or building a SPA on the recovery JSON API? The ARIA wiring above is identical, but the focus strategy is not: a server template relies on page-load behavior, while a SPA owns focus management after every re-render and must move focus and re-announce errors itself. Confirm which rendering mode you run before implementing the focus steps.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.