TanStack / TanStack/router

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

Open Beginner friendly
#8,106 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

information needed
Dominant language
TypeScript
Stars
15.1k
Forks
1.9k
Avg merge
1d 20h
Merged PRs (30d)
143

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the Solid router's useLinkProps entry point, where caller props are copied into linkProps and role is redefined. Compare the enabled and disabled cases with the minimal reproducer and its react-control comparison. Done means enabled links preserve a caller's role, while disabled links retain the adapter's role="link" behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
accessibility, frontend, web-dev
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.