Skip to content

fix(link): added delegateFocus for Safari tab index issue in shadow root #5580

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

Rajdeepc
Copy link
Contributor

@Rajdeepc Rajdeepc commented Jun 28, 2025

Description

SWC Link components <sp-link> were not keyboard navigable on Safari/WebKit browsers, particularly when using external keyboards on iOS devices. Users could not Tab through links or use Enter/Space to activate them, making the component inaccessible in Safari.

Motivation and context

Root Cause

  1. The original implementation had several issues that prevented proper keyboard navigation in Safari:
  2. Shadow DOM Focus Management: Safari has stricter requirements for custom elements and shadow DOM focus delegation
  3. Missing Explicit Tabindex: The anchor element inside shadow DOM didn't have explicit tabindex="0", which Safari requires for keyboard navigation
  4. Focus Delegation Issues: Safari doesn't always properly delegate focus from custom elements to internal shadow DOM elements
  5. iOS WebView Limitations: iOS Safari has additional restrictions on keyboard navigation with custom elements

Key Changes

  1. Added delegatesFocus: true to shadow root configuration
   static override shadowRootOptions = {
       mode: 'open' as const,
       delegatesFocus: true,
   };
  1. Set explicit tabindex="0" on anchor element
   return this.renderAnchor({ 
       id: 'anchor',
       tabindex: 0,
   });

Breaking Changes

None. This is a non-breaking enhancement that improves Safari compatibility while maintaining all existing APIs, styling, and behavior.

Related issue(s)

Screenshots (if appropriate)


Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • Safari Test

    1. Go here
    2. Open Safari
    3. Press Tab and see the link is highlighted
    4. Press Space or Enter, link action fires
  • Chrome Test

    1. Go here
    2. Open Chrome
    3. Press Tab and see the link is highlighted
    4. Press Space or Enter, link action fires
  • Firefox Test

    1. Go here
    2. Open Firefox
    3. Press Tab and see the link is highlighted
    4. Press Space or Enter, link action fires

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

Copy link

changeset-bot bot commented Jun 28, 2025

🦋 Changeset detected

Latest commit: c4e0daf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 84 packages
Name Type
@spectrum-web-components/link Patch
@spectrum-web-components/breadcrumbs Patch
@spectrum-web-components/custom-vars-viewer Patch
@spectrum-web-components/bundle Patch
documentation Patch
@spectrum-web-components/eslint-plugin Patch
@spectrum-web-components/accordion Patch
@spectrum-web-components/action-bar Patch
@spectrum-web-components/action-button Patch
@spectrum-web-components/action-group Patch
@spectrum-web-components/action-menu Patch
@spectrum-web-components/alert-banner Patch
@spectrum-web-components/alert-dialog Patch
@spectrum-web-components/asset Patch
@spectrum-web-components/avatar Patch
@spectrum-web-components/badge Patch
@spectrum-web-components/button-group Patch
@spectrum-web-components/button Patch
@spectrum-web-components/card Patch
@spectrum-web-components/checkbox Patch
@spectrum-web-components/clear-button Patch
@spectrum-web-components/close-button Patch
@spectrum-web-components/coachmark Patch
@spectrum-web-components/color-area Patch
@spectrum-web-components/color-field Patch
@spectrum-web-components/color-handle Patch
@spectrum-web-components/color-loupe Patch
@spectrum-web-components/color-slider Patch
@spectrum-web-components/color-wheel Patch
@spectrum-web-components/combobox Patch
@spectrum-web-components/contextual-help Patch
@spectrum-web-components/dialog Patch
@spectrum-web-components/divider Patch
@spectrum-web-components/dropzone Patch
@spectrum-web-components/field-group Patch
@spectrum-web-components/field-label Patch
@spectrum-web-components/help-text Patch
@spectrum-web-components/icon Patch
@spectrum-web-components/icons-ui Patch
@spectrum-web-components/icons-workflow Patch
@spectrum-web-components/icons Patch
@spectrum-web-components/iconset Patch
@spectrum-web-components/illustrated-message Patch
@spectrum-web-components/infield-button Patch
@spectrum-web-components/menu Patch
@spectrum-web-components/meter Patch
@spectrum-web-components/modal Patch
@spectrum-web-components/number-field Patch
@spectrum-web-components/overlay Patch
@spectrum-web-components/picker-button Patch
@spectrum-web-components/picker Patch
@spectrum-web-components/popover Patch
@spectrum-web-components/progress-bar Patch
@spectrum-web-components/progress-circle Patch
@spectrum-web-components/radio Patch
@spectrum-web-components/search Patch
@spectrum-web-components/sidenav Patch
@spectrum-web-components/slider Patch
@spectrum-web-components/split-view Patch
@spectrum-web-components/status-light Patch
@spectrum-web-components/swatch Patch
@spectrum-web-components/switch Patch
@spectrum-web-components/table Patch
@spectrum-web-components/tabs Patch
@spectrum-web-components/tags Patch
@spectrum-web-components/textfield Patch
@spectrum-web-components/thumbnail Patch
@spectrum-web-components/toast Patch
@spectrum-web-components/tooltip Patch
@spectrum-web-components/top-nav Patch
@spectrum-web-components/tray Patch
@spectrum-web-components/underlay Patch
@spectrum-web-components/story-decorator Patch
@spectrum-web-components/vrt-compare Patch
@spectrum-web-components/base Patch
@spectrum-web-components/grid Patch
@spectrum-web-components/opacity-checkerboard Patch
@spectrum-web-components/reactive-controllers Patch
@spectrum-web-components/shared Patch
@spectrum-web-components/styles Patch
@spectrum-web-components/theme Patch
@spectrum-web-components/truncated Patch
example-project-rollup Patch
example-project-webpack Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

github-actions bot commented Jun 28, 2025

📚 Branch Preview

🔍 Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-5580

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

Copy link

github-actions bot commented Jun 28, 2025

Tachometer results

Chrome

link permalink

test-basic

Version Bytes Avg Time vs remote vs branch
npm latest 429 kB 13.99ms - 14.29ms - faster ✔
5% - 7%
0.69ms - 1.12ms
branch 407 kB 14.89ms - 15.20ms slower ❌
5% - 8%
0.69ms - 1.12ms
-
Firefox

link permalink

test-basic

Version Bytes Avg Time vs remote vs branch
npm latest 429 kB 28.41ms - 30.43ms - unsure 🔍
-10% - +2%
-3.20ms - +0.52ms
branch 407 kB 29.19ms - 32.33ms unsure 🔍
-2% - +11%
-0.52ms - +3.20ms
-

@Rajdeepc Rajdeepc self-assigned this Jun 28, 2025
@Rajdeepc Rajdeepc added bug Something isn't working Component: Link labels Jun 28, 2025
@Rajdeepc Rajdeepc marked this pull request as ready for review June 28, 2025 06:34
@Rajdeepc Rajdeepc requested a review from a team as a code owner June 28, 2025 06:34
* This enables delegatesFocus for Safari compatibility
*/
static override shadowRootOptions = {
...LitElement.shadowRootOptions,
Copy link
Contributor

Choose a reason for hiding this comment

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

We usually use spread ...SpectrumElement.shadowRootOptions, should we also do it here?

expect(anchor.getAttribute('tabindex')).to.eq('0');

// WebKit-specific enhanced tests
if (isWebKit()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we can remove this check and run the assertions for all browsers... any reason we need to scope it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes there is an upstream issue around this. It is a known limitation of iOS/Safari with custom elements and shadow DOM. See WebKit bug reports and related issues.

@Rajdeepc Rajdeepc requested a review from rubencarvalho July 1, 2025 07:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]: SWC Link Component Not Keyboard Navigable in Safari
2 participants