Skip to content

Conversation

@bjorndonald
Copy link

Enable link functionality for tabs with linkUrl on Flutter Web

This PR enables proper link functionality for tabs that have a linkUrl set on Flutter Web. Previously, when using tabs for navigation menus with URLs (e.g., wrapping a Tab with the Link widget from url_launcher), the link semantics were lost because SemanticTab always rendered as a generic <flt-semantics> element with role="tab". fixes #180321

Problem

When tabs are used for app navigation menus on web:

  • Users cannot right-click or tap a menu item to copy its URL
  • Links for menu items are not indexed by search engines
  • The native browser link behavior is completely lost

Solution

Modified SemanticTab to:

  1. Render as an <a> element when linkUrl is present, while maintaining role="tab" for accessibility
  2. Set the href attribute based on the linkUrl value
  3. Update the href attribute dynamically when linkUrl changes

This results in HTML like <a role="tab" href="https://example.com">Tab Label</a>, which provides:

  • ✅ Proper tab accessibility for screen readers (via role="tab")
  • ✅ Right-click to copy URL functionality
  • ✅ Middle-click to open in new tab
  • ✅ Search engine indexing of navigation links

Before

<flt-semantics role="tab">Tab Label</flt-semantics>

After (when linkUrl is set)

<a role="tab" href="https://example.com" style="display: block;">Tab Label</a>

Fixes https://github.com/flutter/flutter/issues/XXXXX

Pre-launch Checklist

@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

@github-actions github-actions bot added engine flutter/engine related. See also e: labels. a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) platform-web Web applications specifically labels Dec 27, 2025
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request aims to enable link functionality for tabs with a linkUrl on Flutter Web by rendering them as <a> elements. While the implementation correctly handles tabs that have a linkUrl upon creation, it has a critical flaw in handling dynamic updates. If a linkUrl is added or removed after the tab is created, the underlying DOM element type is not updated, leading to incorrect behavior. I've provided a detailed comment explaining the issue and suggesting a more robust architectural solution.

Comment on lines +26 to +50
DomElement createElement() {
// When the tab has a link URL, render as an <a> element to enable
// proper link functionality (right-click copy, SEO indexing) while
// still using role="tab" for accessibility.
if (semanticsObject.hasLinkUrl) {
final DomElement element = domDocument.createElement('a');
element.style.display = 'block';
return element;
}
return super.createElement();
}

@override
void update() {
super.update();

// Update the href attribute when the link URL changes.
if (semanticsObject.isLinkUrlDirty) {
if (semanticsObject.hasLinkUrl) {
element.setAttribute('href', semanticsObject.linkUrl!);
} else {
element.removeAttribute('href');
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

This implementation has a fundamental flaw regarding dynamic updates. The createElement method is only called once when the SemanticRole is instantiated. If a linkUrl is added to or removed from the SemanticsObject later, this method won't be called again, and the DOM element type will not change. This leads to incorrect behavior in the update method.

Specifically:

  • If a tab is created without a linkUrl, it gets a <flt-semantics> element. If linkUrl is added later, the update method will incorrectly set an href attribute on this non-anchor element.
  • If a tab is created with a linkUrl, it gets an <a> element. If linkUrl is removed later, it remains an <a> element, just without an href, which is not ideal.

To correctly handle dynamic changes, the element type needs to be swapped. This is typically done by changing the EngineSemanticsRole kind, which triggers disposal of the old role and creation of a new one with the correct element. This would likely require changes in _getEngineSemanticsRole in semantics.dart to return a different role kind based on hasLinkUrl for tabs.

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

Labels

a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) engine flutter/engine related. See also e: labels. platform-web Web applications specifically

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Link does not work with Tab

1 participant