Skip to content

solid-router 2.0.0-rc.0: <Link> drops a caller’s role on enabled links (react-router keeps it) #8106

Description

@TylerRick

Which project does this relate to?

Router

Describe the bug

role passed to <Link> never reaches the DOM unless the link is disabled. The React adapter keeps it, so the same markup behaves differently on the two adapters:

element solid-router 2.0.0-rc.0 react-router 1.170.27
<Link to="/" role="button"> no role attribute role="button"
<Link to="/" role="button" disabled> role="link" role="link"
<a href="/" role="button"> role="button" role="button"

Cause: useLinkProps copies the caller's props onto linkProps, then redefines role unconditionally, so the getter wins on enabled links —

role: () => local.disabled ? "link" : void 0,

The React adapter merges its { role: 'link', 'aria-disabled': true } only when the link IS disabled (...disabled && STATIC_DISABLED_PROPS), leaving a caller's role alone.

Complete minimal reproducer

https://github.com/TylerRick/tanstack-solid-link-role-repro (the react-control/ folder is the same markup on react-router, for comparison)

Steps to Reproduce the Bug or Issue

  1. pnpm install
  2. pnpm dev
  3. Open http://localhost:5598 and read the role attribute off #enabled, #disabled and #plain#enabled has none.
  4. For the comparison: cd react-control && pnpm install && pnpm dev, then read the same three (#enabled is role="button" there).

Expected behavior

role behaves like any other pass-through attribute: the adapter's own value applies when the link is disabled, and a caller's value is respected otherwise. A fallback rather than an override would do it —

role: () => (local.disabled ? 'link' : propsSafeToSpread.role),

— or define the getter only when local.disabled is set. Happy to PR whichever you prefer.

Screenshots or Videos

No response

Platform

  • Router / Start Version: @tanstack/solid-router 2.0.0-rc.0, @tanstack/router-core 1.171.16 (control: @tanstack/react-router 1.170.27)
  • solid-js / @solidjs/web: 2.0.0-rc.0
  • OS: Linux
  • Browser: Chromium 141
  • Bundler: vite 8.2.1

Additional context

role is an ARIA attribute, so dropping it silently changes what assistive technology announces for
the element — an author who writes role="button" on a <Link> gets a link announced as a link, with
no error and nothing in the DOM to explain why. Ours was a tab strip, where the roles are what make
the widget legible to screen readers; the only way to keep them was to stop using <Link> there and
hand-roll the click interception, which gives up its preloading and active-state handling.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions