Repository: TanStack/router (ts)

Issue:
When using createLink(), activeProps type should allow component props
### Describe the bug

<img width="478" alt="image" src="https://github.com/user-attachments/assets/790b8150-2a4f-4b8b-9cee-482f332daeda">

TS error: `
Object literal may only specify known properties, and 'selected' does not exist in type 'AnchorHTMLAttributes<HTMLAnchorElement> | (() => AnchorHTMLAttributes<HTMLAnchorElement>)'.
`

Beside, no `className` should be provided to component to make this function completely agnostic.

### Your Example Website or App

https://stackblitz.com/edit/github-ayifmj?file=src%2Fmain.tsx,src%2Froutes%2Findex.tsx

### Steps to Reproduce the Bug or Issue

Click on the stackblitz link and notice the TS error.

### Expected behavior

No TS error.

### Screenshots or Videos

_No response_

### Platform

- OS: [e.g. macOS, Windows, Linux]
- Browser: [e.g. Chrome, Safari, Firefox]
- Version: [e.g. 91.1]


### Additional context

_No response_

Pull request description:
fix: allow activeProps to infer component props
Fixes #2079 

Patch:
diff --git a/docs/framework/react/guide/custom-link.md b/docs/framework/react/guide/custom-link.md
new file mode 100644
index 000000000..1d41c024c
--- /dev/null
+++ b/docs/framework/react/guide/custom-link.md
@@ -0,0 +1,87 @@
+---
+title: Custom Link
+---
+
+While repeating yourself can be fine in many cases, you may find yourself repeating yourself too much. Sometimes you may want to make cross cutting components with extra behavior or styles. Or sometimes you may also use third party libraries and combine it with TanStack Router's type safety
+
+# `createLink` for cross cutting concerns
+
+`createLink` creates a custom `Link` component with the same type parameters as `Link`. This means you can create your own component which provides the same type safety and typescript performance as `Link`.
+
+If you want to create a component which wraps `Link` with some additional styles or behavior then you can do so with the following
+
+```tsx
+import { Link, createLink } from '@tanstack/react-router'
+
+export const NavigationLink = createLink(
+  React.forwardRef((props: {}, ref: React.Ref<HTMLAnchorElement>) => {
+    return (
+      <Link
+        {...props}
+        ref={ref}
+        className="block py-2 px-3 text-blue-700"
+        preload="intent"
+        activeProps={{ className: `font-bold` }}
+      />
+    )
+  }),
+)
+```
+
+> React.forwardRef will not be required in React 19 -->
+
+You can then use your newly created `Link` component as any other `Link`
+
+```tsx
+<NavigationLink
+  to="/dashboard/invoices/$invoiceId"
+  params={{
+    invoiceId: 0,
+  }}
+/>
+```
+
+# `createLink` with third party libraries
+
+You might want to use third party component libraries with TanStack Router. For example to use `Button` from MUI you can use `createLink` which infers the types from both `Button` and `Link` while keeping type parameters necessary for TanStack Router's type safety
+
+```tsx
+import { createLink, Link } from '@tanstack/react-router'
+import { Button } from '@mui/material'
+
+const ButtonLink = createLink(
+  React.forwardRef(
+    (
+      props: Parameters<typeof Button>[0],
+      ref: React.Ref<HTMLButtonElement>,
+    ) => {
+      return <Button {...props} ref={ref} component={Link} />
+    },
+  ),
+)
+```
+
+`createLink` will infer types from the component passed to `createLink` and create a new `Link` component with TanStack Router's type parameters necessary for type safety and the additional props from `Button`
+
+`ButtonLink` can then be used with props from both
+
+```tsx
+<ButtonLink to="/about" variant="outlined" />
+```
+
+If using props from `Link` like `to` is needed, you can use `CreateLinkProps`
+
+```tsx
+import { createLink, Link, CreateLinkProps } from '@tanstack/react-router'
+import { Button } from '@mui/material'
+
+const ButtonLink = createLink(
+  React.forwardRef((props: CreateLinkProps & Parameters<typeof Button>[0]) => {
+    return (
+      <Button {...props} component={Link}>
+        Navigate to {props.to}
+      </Button>
+    )
+  }),
+)
+```
diff --git a/packages/react-router/src/index.tsx b/packages/react-router/src/index.tsx
index 913713db4..1560645c9 100644
--- a/packages/react-router/src/index.tsx
+++ b/packages/react-router/src/index.tsx
@@ -73,6 +73,7 @@ export type {
   ActiveLinkOptions,
   LinkProps,
   LinkComponent,
+  CreateLinkProps,
 } from './link'
 
 export type { ParsedLocation } from './location'
diff --git a/packages/react-router/src/link.tsx b/packages/react-router/src/link.tsx
index 4b44a5819..2c728e2b9 100644
--- a/packages/react-router/src/link.tsx
+++ b/packages/react-router/src/link.tsx
@@ -28,6 +28,7 @@ import type {
 } from './routeInfo'
 import type { AnyRouter, RegisteredRouter } from './router'
 import type {
+  Constrain,
   Expand,
   MakeDifferenceOptional,
   NoInfer,
@@ -36,6 +37,7 @@ import type {
   Updater,
   WithoutEmpty,
 } from './utils'
+import type { ReactNode } from 'react'
 
 export type CleanPath<T extends string> = T extends `${infer L}//${infer R}`
   ? CleanPath<`${CleanPath<L>}/${CleanPath<R>}`>
@@ -413,7 +415,7 @@ export type IsRequired<
   TTo,
 > =
   ResolveRelativePath<TFrom, TTo> extends infer TPath
-    ? string extends TPath
+    ? undefined extends TPath
       ? never
       : TPath extends CatchAllPaths<TrailingSlashOptionByRouter<TRouter>>
         ? never
@@ -820,9 +822,9 @@ export function useLinkProps<
   }
 
   return {
+    ...rest,
     ...resolvedActiveProps,
     ...resolvedInactiveProps,
-    ...rest,
     href: disabled
       ? undefined
       : next.maskedLocation
@@ -847,50 +849,65 @@ export function useLinkProps<
   }
 }
 
+type UseLinkReactProps<TComp> = TComp extends keyof JSX.IntrinsicElements
+  ? JSX.IntrinsicElements[TComp]
+  : React.PropsWithoutRef<
+      TComp extends React.ComponentType<infer TProps> ? TProps : never
+    > &
+      React.RefAttributes<
+        TComp extends
+          | React.FC<{ ref: infer TRef }>
+          | React.Component<{ ref: infer TRef }>
+          ? TRef
+          : never
+      >
+
 export type UseLinkPropsOptions<
   TRouter extends AnyRouter = RegisteredRouter,
   TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
   TTo extends string | undefined = '.',
   TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
   TMaskTo extends string = '.',
-> = ActiveLinkOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
-  React.AnchorHTMLAttributes<HTMLAnchorElement>
+> = ActiveLinkOptions<'a', TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
+  UseLinkReactProps<'a'>
 
 export type ActiveLinkOptions<
+  TComp = 'a',
   TRouter extends AnyRouter = RegisteredRouter,
   TFrom extends string = string,
   TTo extends string | undefined = '.',
   TMaskFrom extends string = TFrom,
   TMaskTo extends string = '.',
-> = LinkOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> & ActiveLinkOptionProps
+> = LinkOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
+  ActiveLinkOptionProps<TComp>
 
-type ActiveLinkAnchorProps = Omit<
-  React.AnchorHTMLAttributes<HTMLAnchorElement> & {
+type ActiveLinkProps<TComp> = Partial<
+  LinkComponentReactProps<TComp> & {
     [key: `data-${string}`]: unknown
-  },
-  'children'
+  }
 >
 
-export interface ActiveLinkOptionProps {
+export interface ActiveLinkOptionProps<TComp = 'a'> {
   /**
    * A function that returns additional props for the `active` state of this link.
    * These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
    */
-  activeProps?: ActiveLinkAnchorProps | (() => ActiveLinkAnchorProps)
+  activeProps?: ActiveLinkProps<TComp> | (() => ActiveLinkProps<TComp>)
   /**
    * A function that returns additional props for the `inactive` state of this link.
    * These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
    */
-  inactiveProps?: ActiveLinkAnchorProps | (() => ActiveLinkAnchorProps)
+  inactiveProps?: ActiveLinkProps<TComp> | (() => ActiveLinkProps<TComp>)
 }
 
 export type LinkProps<
+  TComp = 'a',
   TRouter extends AnyRouter = RegisteredRouter,
   TFrom extends string = string,
   TTo extends string | undefined = '.',
   TMaskFrom extends string = TFrom,
   TMaskTo extends string = '.',
-> = ActiveLinkOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
+> = ActiveLinkOptions<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
   LinkPropsChildren
 
 export interface LinkPropsChildren {
@@ -903,32 +920,29 @@ export interface LinkPropsChildren {
       }) => React.ReactNode)
 }
 
-type LinkComponentReactProps<TComp> = React.PropsWithoutRef<
-  TComp extends React.FC<infer TProps> | React.Component<infer TProps>
-    ? TProps
-    : TComp extends keyof React.JSX.IntrinsicElements
-      ? Omit<React.HTMLProps<TComp>, 'children' | 'preload'>
-      : never
-> &
-  React.RefAttributes<
-    TComp extends
-      | React.FC<{ ref: infer TRef }>
-      | React.Component<{ ref: infer TRef }>
-      ? TRef
-      : TComp extends keyof React.JSX.IntrinsicElements
-        ? React.ComponentRef<TComp>
-        : never
-  >
+type LinkComponentReactProps<TComp> = Omit<
+  UseLinkReactProps<TComp>,
+  keyof CreateLinkProps
+>
 
 export type LinkComponentProps<
-  TComp,
+  TComp = 'a',
   TRouter extends AnyRouter = RegisteredRouter,
   TFrom extends string = string,
   TTo extends string | undefined = '.',
   TMaskFrom extends string = TFrom,
   TMaskTo extends string = '.',
 > = LinkComponentReactProps<TComp> &
-  LinkProps<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>
+  LinkProps<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo>
+
+export type CreateLinkProps = LinkProps<
+  any,
+  any,
+  string,
+  string,
+  string,
+  string
+>
 
 export type LinkComponent<TComp> = <
   TRouter extends RegisteredRouter = RegisteredRouter,
@@ -940,7 +954,9 @@ export type LinkComponent<TComp> = <
   props: LinkComponentProps<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
 ) => React.ReactElement
 
-export function createLink<const TComp>(Comp: TComp): LinkComponent<TComp> {
+export function createLink<const TComp>(
+  Comp: Constrain<TComp, any, (props: CreateLinkProps) => ReactNode>,
+): LinkComponent<TComp> {
   return React.forwardRef(function CreatedLink(props, ref) {
     return <Link {...(props as any)} _asChild={Comp} ref={ref} />
   }) as any
diff --git a/packages/react-router/src/route.ts b/packages/react-router/src/route.ts
index 225d9e45b..156494b2e 100644
--- a/packages/react-router/src/route.ts
+++ b/packages/react-router/src/route.ts
@@ -1555,7 +1555,7 @@ export type RouteMask<TRouteTree extends AnyRoute> = {
 
 export function createRouteMask<
   TRouteTree extends AnyRoute,
-  TFrom extends RoutePaths<TRouteTree> | string,
+  TFrom extends string,
   TTo extends string,
 >(
   opts: {
diff --git a/packages/react-router/src/routeInfo.ts b/packages/react-router/src/routeInfo.ts
index 56d8df919..f68682ea4 100644
--- a/packages/react-router/src/routeInfo.ts
+++ b/packages/react-router/src/routeInfo.ts
@@ -95,11 +95,13 @@ export type CodeRoutePaths<TRouteTree extends AnyRoute> =
     ? TRoutes['fullPath']
     : never
 
-export type RoutePaths<TRouteTree extends AnyRoute> =
-  | (InferFileRouteTypes<TRouteTree> extends never
-      ? CodeRoutePaths<TRouteTree>
-      : InferFileRouteTypes<TRouteTree>['fullPaths'])
-  | '/'
+export type RoutePaths<TRouteTree extends AnyRoute> = unknown extends TRouteTree
+  ? string
+  :
+      | (InferFileRouteTypes<TRouteTree> extends never
+          ? CodeRoutePaths<TRouteTree>
+          : InferFileRouteTypes<TRouteTree>['fullPaths'])
+      | '/'
 
 export type RouteToPathAlwaysTrailingSlash<TRoute extends AnyRoute> =
   TRoute['path'] extends '/'
@@ -160,8 +162,9 @@ export type FileRouteToPath<
 export type RouteToPath<
   TRouter extends AnyRouter,
   TRouteTree extends AnyRoute,
-> =
-  InferFileRouteTypes<TRouter['routeTree']> extends never
+> = unknown extends TRouter
+  ? string
+  : InferFileRouteTypes<TRouter['routeTree']> extends never
     ? CodeRouteToPath<TRouter, TRouteTree>
     : FileRouteToPath<TRouter>
 


Test patch:
diff --git a/packages/react-router/tests/link.test-d.tsx b/packages/react-router/tests/link.test-d.tsx
index 065df188a..f4527e76f 100644
--- a/packages/react-router/tests/link.test-d.tsx
+++ b/packages/react-router/tests/link.test-d.tsx
@@ -1,6 +1,12 @@
 import { expectTypeOf, test } from 'vitest'
-import { Link, createRootRoute, createRoute, createRouter } from '../src'
-import type { SearchSchemaInput } from '../src'
+import {
+  Link,
+  createLink,
+  createRootRoute,
+  createRoute,
+  createRouter,
+} from '../src'
+import type { CreateLinkProps, SearchSchemaInput } from '../src'
 
 const rootRoute = createRootRoute({
   validateSearch: (): { rootPage?: number } => ({ rootPage: 0 }),
@@ -3541,3 +3547,295 @@ test('when navigating to a route with SearchSchemaInput', () => {
     .parameter(0)
     .toEqualTypeOf<{ rootPage?: number; page?: number; linesPage: number }>()
 })
+
+test('when passing a component with props to createLink and navigating to the root', () => {
+  const MyLink = createLink((props: { additionalProps: number }) => (
+    <Link {...(props as {})} />
+  ))
+
+  const DefaultRouterLink = MyLink<DefaultRouter, string, '/'>
+  const DefaultRouterObjectsLink = MyLink<DefaultRouterObjects, string, '/'>
+  const RouterAlwaysTrailingSlashLink = MyLink<
+    RouterAlwaysTrailingSlashes,
+    string,
+    '/'
+  >
+  const RouterNeverTrailingSlashLink = MyLink<
+    RouterNeverTrailingSlashes,
+    string,
+    '/'
+  >
+  const RouterPreserveTrailingSlashLink = MyLink<
+    RouterPreserveTrailingSlashes,
+    string,
+    '/'
+  >
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('to')
+    .toEqualTypeOf<
+      | '..'
+      | '.'
+      | '/'
+      | '/invoices'
+      | '/invoices/$invoiceId'
+      | '/invoices/$invoiceId/details/$detailId'
+      | '/invoices/$invoiceId/details'
+      | '/invoices/$invoiceId/details/$detailId/lines'
+      | '/invoices/$invoiceId/edit'
+      | '/posts'
+      | '/posts/$postId'
+      | undefined
+    >()
+
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('to')
+    .toEqualTypeOf<
+      | '..'
+      | '.'
+      | '/'
+      | '/invoices'
+      | '/invoices/$invoiceId'
+      | '/invoices/$invoiceId/details/$detailId'
+      | '/invoices/$invoiceId/details/$detailId/lines'
+      | '/invoices/$invoiceId/details'
+      | '/invoices/$invoiceId/edit'
+      | '/posts'
+      | '/posts/$postId'
+      | undefined
+    >()
+
+  expectTypeOf(RouterAlwaysTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('to')
+    .toEqualTypeOf<
+      | '../'
+      | './'
+      | '/'
+      | '/invoices/'
+      | '/invoices/$invoiceId/'
+      | '/invoices/$invoiceId/details/$detailId/'
+      | '/invoices/$invoiceId/details/$detailId/lines/'
+      | '/invoices/$invoiceId/details/'
+      | '/invoices/$invoiceId/edit/'
+      | '/posts/'
+      | '/posts/$postId/'
+      | undefined
+    >()
+
+  expectTypeOf(RouterNeverTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('to')
+    .toEqualTypeOf<
+      | '..'
+      | '.'
+      | '/'
+      | '/invoices'
+      | '/invoices/$invoiceId'
+      | '/invoices/$invoiceId/details/$detailId'
+      | '/invoices/$invoiceId/details/$detailId/lines'
+      | '/invoices/$invoiceId/details'
+      | '/invoices/$invoiceId/edit'
+      | '/posts'
+      | '/posts/$postId'
+      | undefined
+    >()
+
+  expectTypeOf(RouterPreserveTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('to')
+    .toEqualTypeOf<
+      | '..'
+      | '.'
+      | './'
+      | '../'
+      | '/'
+      | '/invoices'
+      | '/invoices/'
+      | '/invoices/$invoiceId'
+      | '/invoices/$invoiceId/'
+      | '/invoices/$invoiceId/details/$detailId'
+      | '/invoices/$invoiceId/details/$detailId/'
+      | '/invoices/$invoiceId/details/$detailId/lines'
+      | '/invoices/$invoiceId/details/$detailId/lines/'
+      | '/invoices/$invoiceId/details'
+      | '/invoices/$invoiceId/details/'
+      | '/invoices/$invoiceId/edit'
+      | '/invoices/$invoiceId/edit/'
+      | '/posts'
+      | '/posts/'
+      | '/posts/$postId'
+      | '/posts/$postId/'
+      | undefined
+    >()
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
+  expectTypeOf(RouterAlwaysTrailingSlashLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
+  expectTypeOf(RouterNeverTrailingSlashLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
+  expectTypeOf(RouterPreserveTrailingSlashLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(RouterAlwaysTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(RouterNeverTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(RouterPreserveTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<{
+      page?: number
+      rootIndexPage?: number
+      rootPage?: number
+      linesPage?: number
+    }>()
+
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<{
+      page?: number
+      rootIndexPage?: number
+      rootPage?: number
+      linesPage?: number
+    }>()
+
+  expectTypeOf(RouterAlwaysTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<{
+      page?: number
+      rootIndexPage?: number
+      rootPage?: number
+      linesPage?: number
+    }>()
+
+  expectTypeOf(RouterNeverTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<{
+      page?: number
+      rootIndexPage?: number
+      rootPage?: number
+      linesPage?: number
+    }>()
+
+  expectTypeOf(RouterPreserveTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<{
+      page?: number
+      rootIndexPage?: number
+      rootPage?: number
+      linesPage?: number
+    }>()
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(RouterAlwaysTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(RouterNeverTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(RouterPreserveTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('additionalProps')
+    .toEqualTypeOf<number>()
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('activeProps')
+    .returns.toHaveProperty('additionalProps')
+    .toEqualTypeOf<number | undefined>()
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('activeProps')
+    .exclude<(...args: Array<any>) => any>()
+    .toEqualTypeOf<
+      | {
+          [x: `data-${string}`]: unknown
+          ref?: React.LegacyRef<never> | undefined
+          additionalProps?: number | undefined
+          key?: React.Key | null | undefined
+        }
+      | undefined
+    >()
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('activeProps')
+    .extract<(...args: Array<any>) => any>()
+    .returns.toEqualTypeOf<{
+      [x: `data-${string}`]: unknown
+      ref?: React.LegacyRef<never> | undefined
+      additionalProps?: number | undefined
+      key?: React.Key | null | undefined
+    }>()
+
+  createLink((props) => expectTypeOf(props).toEqualTypeOf<CreateLinkProps>())
+})
diff --git a/packages/react-router/tests/link.test.tsx b/packages/react-router/tests/link.test.tsx
index 76ca8fa9b..88a225fd5 100644
--- a/packages/react-router/tests/link.test.tsx
+++ b/packages/react-router/tests/link.test.tsx
@@ -3710,4 +3710,93 @@ describe('createLink', () => {
     expect(customElement.hasAttribute('foo')).toBe(true)
     expect(customElement.getAttribute('foo')).toBe('bar')
   })
+
+  it('should pass activeProps and inactiveProps to the custom link', async () => {
+    const Button: React.FC<
+      React.PropsWithChildren<{
+        active?: boolean
+        foo?: boolean
+        overrideMeIfYouWant: string
+      }>
+    > = ({ active, foo, children, ...props }) => (
+      <button {...props}>
+        active: {active ? 'yes' : 'no'} - foo: {foo ? 'yes' : 'no'} - {children}
+      </button>
+    )
+
+    const ButtonLink = createLink(Button)
+
+    const rootRoute = createRootRoute()
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => (
+        <>
+          <ButtonLink
+            to="/"
+            overrideMeIfYouWant="Button1"
+            activeProps={{
+              active: true,
+              'data-hello': 'world',
+              overrideMeIfYouWant: 'overridden-by-activeProps',
+            }}
+            inactiveProps={{ foo: true }}
+          >
+            Button1
+          </ButtonLink>
+          <ButtonLink
+            to="/posts"
+            overrideMeIfYouWant="Button2"
+            activeProps={{
+              active: true,
+              'data-hello': 'world',
+            }}
+            inactiveProps={{
+              foo: true,
+              'data-hello': 'void',
+              overrideMeIfYouWant: 'overridden-by-inactiveProps',
+            }}
+          >
+            Button2
+          </ButtonLink>
+          <ButtonLink
+            to="/posts"
+            overrideMeIfYouWant="Button3"
+            activeProps={{
+              active: true,
+            }}
+            inactiveProps={{
+              active: false,
+            }}
+          >
+            Button3
+          </ButtonLink>
+        </>
+      ),
+    })
+    const postsRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/posts',
+    })
+    const router = createRouter({
+      routeTree: rootRoute.addChildren([indexRoute, postsRoute]),
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const button1 = await screen.findByText('active: yes - foo: no - Button1')
+    expect(button1.getAttribute('data-hello')).toBe('world')
+    expect(button1.getAttribute('overrideMeIfYouWant')).toBe(
+      'overridden-by-activeProps',
+    )
+
+    const button2 = await screen.findByText('active: no - foo: yes - Button2')
+    expect(button2.getAttribute('data-hello')).toBe('void')
+    expect(button2.getAttribute('overrideMeIfYouWant')).toBe(
+      'overridden-by-inactiveProps',
+    )
+
+    const button3 = await screen.findByText('active: no - foo: no - Button3')
+    expect(button3.getAttribute('overrideMeIfYouWant')).toBe('Button3')
+  })
 })