fix(tabs): make NSTabs keyboard operable (WCAG 2.1.1) - #338
Open
DipeshRajoria007 wants to merge 1 commit into
Open
fix(tabs): make NSTabs keyboard operable (WCAG 2.1.1)#338DipeshRajoria007 wants to merge 1 commit into
DipeshRajoria007 wants to merge 1 commit into
Conversation
NSTabs rendered role="tablist" on the container but each tab was a click-only div with no role="tab", tabIndex, aria-selected, or keyboard handler, so keyboard and screen-reader users could neither reach nor operate the tabs. This transplants the accessible pattern grauity already ships in the sibling NSTabList/NSTab, with no change to the public contract or existing runtime behavior. - each item now renders role="tab", aria-selected, and a roving tabIndex (exactly one tab tabbable; on mount the selected one, clamped so an out-of-range initialActiveTab can't leave every tab untabbable) - container onKeyDown: Arrow Left/Right (wraparound), Home, End, and Enter/Space to select — manual activation (arrows move focus; Enter/Space/click commit), so onTabFocusChange fires on the exact same events as before (mount + commit, never on arrow) - add a :focus-visible ring reusing the Tab/TabList brand token - add an optional ariaLabel prop (default 'Tab list') to name the tablist - tests for roles/aria-selected/roving tabindex/keyboard + a KeyboardNavigation story for the a11y addon TabsProps only gains an optional ariaLabel; nothing removed, renamed, or retyped. The item stays a div (no UA button reset, no nested-interactive risk with arbitrary ReactNode items).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
NSTabs(ui/elements/Tabs) was not keyboard operable — a WCAG 2.1.1 (Keyboard) failure. The container carriedrole="tablist", but each tab item was a click-onlydivwith norole="tab",tabIndex,aria-selected, or keyboard handler, so keyboard-only and screen-reader users could neither reach nor operate the tabs. Every consumer is affected (the Calendar view switcher; the KoyoGP time picker; downstream apps such as newton-web's TMS resources File/Link switch).This transplants the accessible pattern grauity already ships in the sibling
NSTabList/NSTabintoNSTabs, with no change to the public contract or existing runtime behavior.What changed
role="tab",aria-selected, and a rovingtabIndex(exactly one tab is tabbable; on mount it's the selected tab — clamped so an out-of-rangeinitialActiveTabcan never leave every tab untabbable).:focus-visiblering reusing the same token asNSTab/NSTabList(--border-subtle-brand-default).ariaLabelprop (default'Tab list') to give the tablist an accessible name.KeyboardNavigationstory so the Storybook a11y panel demonstrates the fix.Zero-regression
TabsPropsgains only an optionalariaLabel— nothing removed, renamed, or retyped.onTabFocusChangefires exactly as before: once on mount (initial index) and on every commit (click / Enter / Space). Arrows do not fire it (manual activation), so consumers like the Calendar view switcher see identical callback timing.activeTab+initialActiveTabseeding, string/ReactNodeitem rendering,role="tablist"+classNamepassthrough, and the click path are unchanged. The item stays a<div>(no UA button reset, no nested-interactive risk with arbitrary node items).Out of scope (intentional)
Tab↔tabpanel
aria-controls/idwiring —NSTabsrenders no panels; that would need consumer-supplied ids (a future additive prop), not a regression fix.Verification
npm run typecheckclean ·npm run lintclean ·npm run testgreen (46 suites / 423 tests, including the Calendar suite).Type of Change
Related Issues
Motivated by an accessibility finding during review of a downstream consumer (newton-web PR #8745). No grauity issue filed yet.
Checklist
Additional Notes
Behavior model chosen is manual activation (mirrors
NSTabList) specifically to preserveonTabFocusChange's current firing timing for existing consumers. Automatic activation (selection-follows-focus) was considered and rejected because it would fire the callback on every arrow keypress, changing callback frequency for the Calendar view switcher.