Skip to content

Deprecate Link to prop — standardize on href + LinkProvider#498

Merged
mattrothenberg merged 3 commits intomainfrom
mrothenberg/deprecate-link-to-prop
May 7, 2026
Merged

Deprecate Link to prop — standardize on href + LinkProvider#498
mattrothenberg merged 3 commits intomainfrom
mrothenberg/deprecate-link-to-prop

Conversation

@mattrothenberg
Copy link
Copy Markdown
Collaborator

@mattrothenberg mattrothenberg commented May 7, 2026

Summary

  • Deprecates the to prop on Link and LinkComponentProps in favor of href
  • Adds a dev-mode deprecation warning when to is used
  • Documents the correct LinkProvider integration pattern for framework routers
  • Adds comprehensive tests covering href, LinkProvider, render prop, and the deprecation

Problem

Kumo's Link component accepts both href and to for specifying the destination URL. This causes a real-world bug when applications configure a LinkProvider with a client-side router:

  1. Developer writes <Link href="https://example.com"> (the natural, platform-standard prop)
  2. The LinkProvider wraps a router component (e.g. React Router's <Link>) that reads to for navigation
  3. The router component ignores href — the external link silently breaks, navigating to a broken internal route instead

The workaround has been to use <Link to="https://example.com"> instead, but this doesn't actually work either — React Router's <Link to> does not support fully qualified URLs. It resolves everything as a path within the app.

The full matrix

With a LinkProvider wrapping React Router:

URL Prop Result
/dashboard href Broken — RR ignores href, reads to (undefined)
/dashboard to Works — RR reads to, client-side nav
https://example.com href Broken — RR ignores href
https://example.com to Also broken — RR resolves it as a relative path

No single prop solves external links without the wrapper being explicit about it.

Why to exists today

to is a React Router-ism that was added to LinkComponentProps so the DefaultLinkComponent could accept either prop. This leaked a routing-framework concept into a presentational component's API.

The scope of the workaround inside Kumo

Five places inside Kumo itself defensively pass to={href} to work around this:

Component Pattern
LinkButton to={typeof href === "string" ? href : undefined}
Sidebar.MenuButton (2 sites) to={href}
DropdownMenu.LinkItem to={href}
Breadcrumbs.Link to={href}

Solution

1. Deprecate to, standardize on href (this PR)

Link should only speak href (the platform primitive). Routing integration is the application's responsibility via LinkProvider. This is inversion of control: Kumo defines the interface, the application injects the routing implementation.

This PR:

  • Deprecates to with @deprecated JSDoc and a dev-mode console.warn
  • Documents LinkProvider with concrete examples for React Router and Next.js
  • Adds tests covering href, LinkProvider, render prop, backwards compatibility, and the deprecation warning

to is not removed — it continues to work for backwards compatibility. Consumers get a dev-mode warning guiding them to migrate.

2. Consumer apps build a smart LinkProvider wrapper (downstream follow-up)

The recommended pattern is for consumer apps to provide a LinkProvider component that:

  • Maps href to the router's navigation prop for internal links
  • Renders a plain <a> for external links
  • Handles target, rel, etc. as appropriate
const AppLink = forwardRef(({ href, to, ...rest }, ref) => {
  const destination = href ?? to;
  const isExternal = destination?.startsWith("http") &&
    new URL(destination).origin !== window.location.origin;

  if (isExternal) {
    return <a ref={ref} href={destination} {...rest} />;
  }
  return <RouterLink ref={ref} to={destination} {...rest} />;
});

<LinkProvider component={AppLink}>
  <App />
</LinkProvider>

Engineers then just use href everywhere — the wrapper handles the rest:

<Link href="/dashboard">Dashboard</Link>
<Link href="https://example.com" target="_blank" rel="noopener noreferrer">
  Cloudflare Docs <Link.ExternalIcon />
</Link>

For exceptional cases where direct router control is needed, the render prop remains as an escape hatch:

<Link render={<RouterLink to="/special" />}>Special case</Link>

Breaking change?

Soft deprecation only. to continues to work — consumers just get a dev-mode warning guiding them to migrate. A future major version could remove to entirely.

  • Reviews
    • bonk has reviewed the change
    • automated review not possible because: RFC for team discussion, requesting feedback on approach
  • Tests
    • Tests included/updated

…tion

Link is a presentational component — routing belongs in the application
layer. The `to` prop is a router-framework concept that leaked into the
component API, causing confusion about which prop to use for navigation.

- Add @deprecated JSDoc on `to` in LinkComponentProps
- Add dev-mode console.warn when `to` is used on Link
- Document LinkProvider integration patterns (React Router, Next.js)
- Add comprehensive tests for href, LinkProvider, render prop, and
  the to→href migration path
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 7, 2026

npm i https://pkg.pr.new/@cloudflare/kumo@498

commit: 93aecea

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

Docs Preview

View docs preview

Commit: e57ecf9

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

Visual Regression Report — 2 changed, 0 unchanged

2 screenshot(s) with visual changes:

Dialog (Open)

18,554,696 px (92.34%) changed

Before After Diff
Before After Diff

Select (Open)

23,137,813 px (93.41%) changed

Before After Diff
Before After Diff

Generated by Kumo Visual Regression

@mattrothenberg mattrothenberg changed the title RFC: Deprecate Link to prop — standardize on href + LinkProvider Deprecate Link to prop — standardize on href + LinkProvider May 7, 2026
- Add LinkProvider integration section with React Router example
- Document render prop as escape hatch for exceptional cases
- Add to prop as deprecated in API reference table
- Update framework integration guidelines
- Update installation page LinkProvider snippet
@mattrothenberg
Copy link
Copy Markdown
Collaborator Author

/bonk review and approve please

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

Review: Approve

This is a well-executed soft deprecation that addresses a real architectural issue.

What I like

Clean inversion of control: Moving routing-framework concerns out of the presentational component and into LinkProvider is the right pattern. Kumo defines the interface (href), apps inject the implementation.

Thorough documentation: The updated docs explain not just what changed, but why and how to migrate. The React Router and Next.js examples in link-provider.tsx are concrete and copy-pasteable.

Comprehensive test coverage: The 26 tests cover:

  • href behavior (standard usage)
  • LinkProvider integration patterns
  • render prop bypass behavior
  • Backwards compatibility for to
  • Deprecation warning emission
  • The "footgun" case where a wrapper only reads to

Safe migration path: The to prop still works, just emits a dev-mode warning. No breaking changes.

Implementation details verified

  • console.warn only fires in dev (guarded by process.env.NODE_ENV)
  • ✅ One-time warning via empty deps useEffect
  • @deprecated JSDoc on both the type and usage sites
  • DefaultLinkComponent still handles href ?? to for backwards compatibility
  • ✅ Changeset present with correct patch classification

Minor notes

The visual regression changes (Dialog, Select) appear unrelated to this PR — likely baseline timing differences with open states.

Suggestion for follow-up

Consider adding a lint rule (similar to no-deprecated-props) to catch to={...} usage at build time, not just runtime.


Great work on the RFC-quality PR description and the thorough test coverage. This is how deprecations should be done.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 7, 2026

I've reviewed and approved PR #498.

Summary

Changes:

  • Deprecates the to prop on Link in favor of href (platform standard)
  • Adds dev-mode deprecation warning via console.warn
  • Documents LinkProvider integration patterns for React Router and Next.js
  • Adds 26 comprehensive tests covering all scenarios

Key points from my review:

  • ✅ Clean architectural fix — routing concerns belong in LinkProvider, not the presentational component
  • ✅ Soft deprecation — to still works, just warns in dev
  • ✅ Excellent test coverage including edge cases
  • ✅ Well-documented with concrete migration examples
  • ✅ Changeset properly classified as patch

The failing tests in CI are unrelated to this PR — they're CLI tests that depend on ai/component-registry.json being present (auto-generated at build time).

github run

Copy link
Copy Markdown
Contributor

@1000hz 1000hz left a comment

Choose a reason for hiding this comment

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

LGTM!

@mattrothenberg mattrothenberg merged commit 0414c54 into main May 7, 2026
19 checks passed
@mattrothenberg mattrothenberg deleted the mrothenberg/deprecate-link-to-prop branch May 7, 2026 18:28
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.

2 participants