Easily style active links in Tanstack Router

TanStack Router gives you two ways to style the active link in a navbar — activeProps for component-level prop merging, and a data-status="active" attribute on the rendered anchor for CSS-only targeting. Here's both, with the gotchas.

With activeProps

1import { Link } from '@tanstack/react-router' 2 3const items = [ 4 { name: 'Dashboard', to: '/dashboard' }, 5 { name: 'Messages', to: '/messages' }, 6] as const 7 8export function MainNav() { 9 return ( 10 <nav className='hidden md:flex items-center space-x-4 lg:space-x-6 mx-4 text-gray-500 dark:text-gray-400'> 11 {items.map(({ name, to }) => ( 12 <Link 13 key={name} 14 to={to} 15 className='text-sm font-medium transition-colors hover:text-black dark:hover:text-white' 16 activeProps={{ className: 'text-black dark:text-white' }} 17 > 18 {name} 19 </Link> 20 ))} 21 </nav> 22 ) 23}

activeProps shallow-merges into the Link's props when the route matches — className strings concatenate, other props overwrite.

You can also pass a function if you need to read the current state:

1activeProps={(state) => ({ 2 className: state.location.pathname.startsWith('/admin') 3 ? 'text-red-600' 4 : 'text-black', 5})}

inactiveProps does the same for the not-matching case.

The / gotcha: activeOptions

By default Link to='/' is considered active for any nested route too — /dashboard, /messages, all of them. That's almost never what you want for the home link. Pin it with activeOptions:

1<Link to='/' activeProps={{ className: 'text-black' }} activeOptions={{ exact: true }}> 2 Home 3</Link>

activeOptions also takes includeSearch and includeHash if those should factor into the match.

CSS-only with data-status

Every TanStack <Link> renders an <a> with data-status="active" when matched. That means you can skip activeProps entirely and target it with CSS — or Tailwind's data-attribute variant:

1<Link 2 to={to} 3 className='text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white data-[status=active]:text-black dark:data-[status=active]:text-white' 4> 5 {name} 6</Link>

Cleaner if you're happy with one className expression. activeProps wins when the active styling diverges enough from the inactive styling that splitting them is more readable.

Type-safe to

Since router v1 stable (Dec 2023), to='/dashboard' is type-checked against your route tree at build time — no need to import route objects (dashboardRoute.fullPath) or compose paths manually. The same API carries over to TanStack Start, the SSR meta-framework built on the router.

Docs: Navigation.