---
name: unity-navigation
description: >
  Load when building Unity links, tabs, breadcrumbs, nav, or pagination. Use it
  to choose router-aware Tanstack Router components versus Raw* href
  primitives.
type: core
library: '@payfit/unity-components'
library_version: '2.x'
sources:
  - 'PayFit/hr-apps:libs/shared/unity/components/src/components/link/RawLink.tsx'
  - 'PayFit/hr-apps:libs/shared/unity/components/src/components/breadcrumbs/Breadcrumbs.tsx'
  - 'PayFit/hr-apps:libs/shared/unity/components/src/components/pagination/Pagination.tsx'
  - 'PayFit/hr-apps:libs/shared/unity/components/src/components/tabs/Tabs.tsx'
  - 'PayFit/hr-apps:libs/shared/unity/components/src/integrations/tanstack-router/index.ts'
  - 'PayFit/hr-apps:libs/shared/unity/components/src/providers/router/RouterProvider.tsx'
  - 'PayFit/hr-apps:libs/shared/unity/components/src/docs/concepts/navigation/Navigation Patterns Explained.mdx'
---

Two entry points expose the same components in two flavors. The base entry
(`@payfit/unity-components`) ships router-agnostic `Raw*` primitives that take
a plain `href`. The Tanstack integration entry
(`@payfit/unity-components/integrations/tanstack-router`) ships type-safe
counterparts that take `to` and are wired via `createLink` from
`@tanstack/react-router`.

## Setup

In a Tanstack Router feature plugin, import every link/nav component from the
integration entry. No `RouterProvider` is required — the integration uses
Tanstack hooks directly.

```tsx
import {
  Breadcrumb,
  BreadcrumbLink,
  Breadcrumbs,
  Link,
  Tab,
  TabList,
  TabPanel,
  Tabs,
} from '@payfit/unity-components/integrations/tanstack-router'
import { Outlet } from '@tanstack/react-router'

export function EmployeesShell() {
  return (
    <>
      <Breadcrumbs>
        <Breadcrumb>
          <BreadcrumbLink to="/">Home</BreadcrumbLink>
        </Breadcrumb>
        <Breadcrumb>
          <BreadcrumbLink to="/employees">Employees</BreadcrumbLink>
        </Breadcrumb>
      </Breadcrumbs>

      <Tabs>
        <TabList>
          <Tab to="/employees/active">Active</Tab>
          <Tab to="/employees/archived">Archived</Tab>
        </TabList>
        <TabPanel>
          <Outlet />
        </TabPanel>
      </Tabs>

      <Link to="/employees/new">New employee</Link>
    </>
  )
}
```

## Core Patterns

### Raw and integration map

| Base entry (`@payfit/unity-components`) | Integration (`@payfit/unity-components/integrations/tanstack-router`) | Prop                                    |
| --------------------------------------- | --------------------------------------------------------------------- | --------------------------------------- |
| `RawLink`                               | `Link`                                                                | `href` (raw) / `to` (integration)       |
| `RawNavItem`                            | `NavItem`                                                             | same                                    |
| `RawBreadcrumbLink`                     | `BreadcrumbLink`                                                      | same                                    |
| `RawPaginationLink`                     | `PaginationLink`                                                      | same                                    |
| `RawTab` (+ `Tabs`)                     | `Tab` (+ `Tabs`)                                                      | route-aware `to` selects the active tab |
| `RawLinkButton`                         | `LinkButton`                                                          | same                                    |

The integration components are produced via `createLink(RawLink)`. They render
exactly the same DOM as the raw versions but accept Tanstack's typed `to`,
`params`, `search`, and `preload` props.

### Compose Breadcrumbs / Pagination / Tabs

Each navigation composite has a strict child contract. Direct children of the
container must be the wrapper part — loose children are silently filtered.

```tsx
import {
  Breadcrumb,
  BreadcrumbLink,
  Breadcrumbs,
  Pagination,
  PaginationContent,
  PaginationItem,
  RawPaginationLink,
  RawTab,
  TabList,
  TabPanel,
  Tabs,
} from '@payfit/unity-components'

export function CompositionExamples({
  page,
  pageCount,
  onPageChange,
}: {
  page: number
  pageCount: number
  onPageChange: (next: number, prev: number, dir: -1 | 1) => void
}) {
  return (
    <>
      <Breadcrumbs>
        <Breadcrumb>
          <BreadcrumbLink href="/">Home</BreadcrumbLink>
        </Breadcrumb>
        <Breadcrumb>
          <BreadcrumbLink href="/employees">Employees</BreadcrumbLink>
        </Breadcrumb>
      </Breadcrumbs>

      <Pagination
        currentPage={page}
        pageCount={pageCount}
        onPageChange={onPageChange}
      >
        <PaginationContent>
          {Array.from({ length: pageCount }, (_, i) => i + 1).map(n => (
            <PaginationItem key={n}>
              <RawPaginationLink value={n} isActive={n === page}>
                {n}
              </RawPaginationLink>
            </PaginationItem>
          ))}
        </PaginationContent>
      </Pagination>

      <Tabs>
        <TabList>
          <RawTab id="active">Active</RawTab>
          <RawTab id="archived">Archived</RawTab>
        </TabList>
        <TabPanel id="active">Active rows</TabPanel>
        <TabPanel id="archived">Archived rows</TabPanel>
      </Tabs>
    </>
  )
}
```

### RouterProvider for Raw\* in non-Tanstack apps

When you use `Raw*` components in an app that uses a different router (e.g.
React Router), wrap the app once in `RouterProvider` so `isActive` and
client-side navigation work. The Tanstack integration entry does NOT need
this — it reads from Tanstack hooks directly.

```tsx
import { RouterProvider } from '@payfit/unity-components'
import { matchPath, useLocation, useNavigate } from 'react-router-dom'

export function UnityRouterBridge({ children }: { children: React.ReactNode }) {
  const navigate = useNavigate()
  const location = useLocation()

  return (
    <RouterProvider
      navigate={to => navigate(to)}
      isActive={(to, isExact) =>
        Boolean(
          matchPath({ path: to, end: Boolean(isExact) }, location.pathname),
        )
      }
    >
      {children}
    </RouterProvider>
  )
}
```

## Common Mistakes

### HIGH Import Link from base entry in a Tanstack Router feature

Wrong:

```tsx
import { Link } from '@payfit/unity-components'
;<Link to="/dashboard">Go</Link>
```

Correct:

```tsx
import { Link } from '@payfit/unity-components/integrations/tanstack-router'
;<Link to="/dashboard">Go</Link>
```

The base export "Link" does not exist; agents alias RawLink and pass to=. The prop is silently ignored and the link does not navigate.

Source: integrations/tanstack-router/components/link/Link.tsx (createLink wraps RawLink)

### HIGH Pass to= prop to RawLink

Wrong:

```tsx
import { RawLink } from '@payfit/unity-components'
;<RawLink to="/dashboard">Go</RawLink>
```

Correct:

```tsx
// Use the integration Link for type-safe routing:
import { Link } from '@payfit/unity-components/integrations/tanstack-router'
<Link to="/dashboard">Go</Link>
// OR keep RawLink with a plain href:
<RawLink href="/dashboard">Go</RawLink>
```

RawLink only accepts href. The to prop is silently ignored.

Source: components/link/RawLink.tsx:113-163

### HIGH Expect the v1 monolithic Pagination

Wrong:

```tsx
import { Pagination, ClientSidePagination } from '@payfit/unity-components'
// ClientSidePagination is not exported; Pagination needs children.
<Pagination currentPage={1} pageCount={10} onPageChange={…} />
```

Correct:

```tsx
import { Pagination, PaginationContent, PaginationItem, RawPaginationLink }
  from '@payfit/unity-components'
<Pagination currentPage={1} pageCount={10} onPageChange={…}>
  <PaginationContent>
    <PaginationItem>
      <RawPaginationLink value={1}>1</RawPaginationLink>
    </PaginationItem>
    {/* … */}
  </PaginationContent>
</Pagination>
// Or, for tables, let DataTable handle pagination internally.
```

v2 Pagination is compositional; passing currentPage/pageCount with no children renders an empty container. The old monolithic component (ClientSidePagination) now lives only inside DataTable as an internal — there is no public monolithic export. For standalone pagination, compose it yourself; for paginated tables, use DataTable which has it built in.

Source: components/pagination/Pagination.tsx; index.ts; maintainer interview (ClientSidePagination is internal to DataTable)

### MEDIUM Forget to wrap BreadcrumbLink in Breadcrumb

Wrong:

```tsx
<Breadcrumbs>
  <BreadcrumbLink href="/">Home</BreadcrumbLink>
</Breadcrumbs>
```

Correct:

```tsx
<Breadcrumbs>
  <Breadcrumb>
    <BreadcrumbLink href="/">Home</BreadcrumbLink>
  </Breadcrumb>
</Breadcrumbs>
```

Breadcrumbs filters children to Breadcrumb type; loose BreadcrumbLinks are dropped silently.

Source: components/breadcrumbs/Breadcrumbs.tsx:328-407

### MEDIUM Use Raw\* components without RouterProvider in a non-Tanstack app

Wrong:

```tsx
<App>
  <RawLink href="/dashboard">Go</RawLink>
</App>
```

Correct:

```tsx
<RouterProvider
  isActive={path => router.isActive(path)}
  navigate={router.navigate}
>
  <App>
    <RawLink href="/dashboard">Go</RawLink>
  </App>
</RouterProvider>
```

Raw\* components consume useRouter() context. Without RouterProvider, isActive checks return undefined and active-link styling is lost.

Source: components/link/RawLink.tsx:186; providers/router/RouterProvider.tsx

## See also

- `unity-setup-feature-plugin` — where and how to mount `RouterProvider`,
  and when a feature plugin should switch from base to integration entry.
- `unity-data-table` — DataTable has pagination built in; do not compose
  `Pagination` next to it.
