Navigation TypeScript helper for React
v1.0.0Added in 1.0.0
Router-agnostic links, active-route matching, one NavItem shape and the app shell context that every nav component shares.
Click through the mini CRM: every link goes through a pretend router given to LinkProvider, the address bar updates and the matching item lights up with aria-current. Open a customer and Customers stays active by prefix; switch match to exact and it lets go. The external link opens a new tab and the disabled one ignores clicks and Tab.
Navigation is a free React helper in the React hooks and utilities category, written in TypeScript for React 19. It installs as source with npx wingo-ui@latest add navigation.
Add to a project
Requirements: React 19, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add navigationUsage
Import NavLink from @/lib/navigation.
"use client"
import type { ReactNode } from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { LinkProvider, matchPath, NavLink, useCurrentPath } from "@/lib/navigation"
// app/providers.tsx, rendered by the root layout around {children}
export function Providers({ children }: { children: ReactNode }) {
return (
<LinkProvider component={Link} currentPath={usePathname()}>
{children}
</LinkProvider>
)
}
// any link inside renders through next/link and knows when it is the current page
export function ClientsLink() {
const currentPath = useCurrentPath()
return (
<NavLink href="/customers" active={matchPath("/customers", currentPath)} className="data-active:font-semibold">
Customers
</NavLink>
)
}API
currentPathdefault""stringLinkProvider({ component, currentPath, onNavigate }): the current pathname (next: usePathname()). Every nav component inside finds its active item from it; a component's own currentPath or value prop wins.
componentdefault"a"ComponentType<LinkComponentProps> | "a"LinkProvider: the router link every NavLink renders through (next/link's Link, react-router's Link). Set it once in a client providers file; library components never import next/*.
onNavigate(href: string) => voidLinkProvider: runs when a NavLink starts an in-app navigation (a plain left click on an internal link). useNavigateListener subscribes to the same event, which is how a route progress bar starts on its own.
navigate(href: string) => voidLinkProvider: the router's push (next: useRouter().push). useNavigate() uses it for navigations without an anchor, like a command menu item picked with Enter; without it they fall back to a full page load.
matchdefault"prefix""prefix" | "exact"matchPath(href, currentPath, match) and NavItem.match: prefix keeps /customers active on /customers/42, exact only on /customers. "/" always matches exactly.
NavLink({ href, component?, active?, current?, disabled?, external?, ...anchorProps }) => JSX.ElementAn anchor through the router link: aria-current (current: "page" | "step" | "location") and data-active while active, a real disabled state (no href, aria-disabled, tabIndex -1), and external links (absolute urls to another origin, or external) in a new tab with rel="noopener noreferrer". data-slot="nav-link".
Parts
findActivePath
functionThe active item and its ancestors ([Settings, Team]): an explicit value (an item key) wins, else the most specific href match. Pass groupItems(groups) for grouped navigation. flattenNavItems, navItemText, isExternalHref and normalizePath are exported too. Signature: (items: NavItem[], { value?, currentPath? }) => NavItem[] | null.
useLinkComponent
hookFor component authors: the provider's link component and path with a prop override, a subscription to in-app navigations, and a navigate function (external urls open in a new tab, in-app paths notify listeners and go through the provider's navigate). Signature: (override?) => LinkComponent.
useCurrentPath
hookFor component authors: the provider's link component and path with a prop override, a subscription to in-app navigations, and a navigate function (external urls open in a new tab, in-app paths notify listeners and go through the provider's navigate). Signature: (override?) => string | undefined.
Examples
Settings tabs
Links in a tab bar with exact matching, so General is not active on /settings/billing. Swap DemoLink for next/link in an app.
Dependencies
No dependencies beyond React.
Files
- lib/navigation.tsx
FAQ
Is the Navigation helper free?
Yes. The source is free and needs no account: copy it from the Code tab or install it with the CLI.
How do I install Navigation?
Run npx wingo-ui@latest add navigation. With shadcn, run npx shadcn@latest add https://wingo-ui.com/r/navigation.json.