Skip to content

feat(ui-tabs,ui-view): add roving tabindex and manual activation mode - #2723

Open
git-nandor wants to merge 1 commit into
masterfrom
INSTUI-5181_fix_ui-tabs_focus_and_tabindex_issue
Open

git-nandor wants to merge 1 commit into
masterfrom
INSTUI-5181_fix_ui-tabs_focus_and_tabindex_issue

Conversation

@git-nandor

@git-nandor git-nandor commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Give the Tabs tablist a proper roving tabindex: exactly one tab has tabindex="0" at a time, tracked per tab node instead of via a Focusable wrapper.
  • Add activationMode="manual", so arrow keys move focus without changing selection until Enter or Space is pressed (for screen readers that reserve arrow keys for their own cursor).
  • Add Home/End support to jump to the first/last enabled tab, and set aria-orientation and aria-selected="false" on unselected tabs to match the WAI-ARIA tabs pattern.
  • Fix RTL-mirrored left/right arrows
  • New shouldUseFocusVisible on View (passes :focus-visible through to the already-existing customCSSSelector), so the roving tabindex doesn't make a mouse click paint a focus ring. No-op for every existing View consumer.

DOM output changes: unselected tabs render tabindex="-1" and aria-selected="false" instead of omitting both attributes. Selectors asserting their absence need updating.

Test Plan

  • VoiceOver + activationMode="manual" — the acceptance criterion for the ticket and not coverable by CI: arrow through the strip, confirm Enter and Space select.
  • Focus ring — clicking a tab must show no ring. Tabbing in or arrowing must show one.
  • tabOverflow="scroll" in manual mode — arrow to an off-screen tab and confirm it scrolls out from under the fade overlay.
  • Check new manual activation section in the docs app.

Fixes INSTUI-5181

🤖 Generated with Claude Code

Unselected tabs had no tabindex, so keyboard focus relied on the Focusable wrapper.
This also made arrow navigation depend on an unstable render prop.

Use a proper roving tabindex: one tab gets tabindex="0", the rest -1. Track focus per tab,
move it explicitly, and keep the roving tabindex in sync, falling back to the selected tab when
focus leaves the tablist.

Add activationMode="manual": arrow keys, Home, and End move focus; Enter or Space selects. This
also provides a reliable way to select tabs in VoiceOver, which reserves arrow keys for its
cursor.

Add Home/End support, RTL-aware arrow navigation, and aria-selected="false" for unselected tabs.
Add isTabbable and onFocus to Tabs.Tab, plus shouldUseFocusVisible to View to prevent focus rings
on mouse clicks.

The DOM output changes: unselected tabs now render tabindex="-1" instead of no
attribute, and aria-selected="false" instead of no attribute. Selectors that
test for the absence of those attributes need updating.

INSTUI-5181

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@git-nandor git-nandor self-assigned this Sep 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://instructure.design/pr-preview/pr-2723/

Built to branch gh-pages at 2026-09-17 13:24 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions

Copy link
Copy Markdown
Contributor

Visual regression report

Cypress suite: ✅ Passing

Visual diff: ⚠️ Changes detected.

Status Count
Unchanged 95
Changed 1
New 0
Removed 0

Accessibility (axe): ✅ No violations.

📊 View full report — click a screenshot's ⚠ badge to see each violation boxed on the image, with the offending element named and contrast failures shown as color swatches.

Diff images (1)

tooltip-dark.png — 14357 pixels differ

Baselines come from the visual-baselines branch. They refresh on every merge to master. The Cypress suite line covers the a11y and console-error assertions — a ❌ there means the suite found real issues even if the visual diff is clean.

github-actions Bot pushed a commit that referenced this pull request Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant