A flexible, accessible, and framework-agnostic navigation link component for React.
- Active-state detection
exact,startsWith,includes, or custom regex (pattern)- Override with your own
isActiveFuncorcustomActiveUrl
- Prefetching support
- Hover-triggered prefetch with configurable delay
- Works with React Router, TanStack Router, Wouter, or your own custom prefetcher
- Flexible rendering
- Renders a React Router
<Link>, a plain<a>, a<span>, or any custom element viaas - Fully controllable redirection (
redirection), replace vs push (replace), and navigation delay
- Renders a React Router
- External & disabled links
isExternal→<a target="_blank" rel="noopener noreferrer">disabled→ renders a<span>witharia-disabled
- Styling & class names
className,activeClassName,inActiveClassNameactiveStyle,inactiveStyle
- Dynamic children
- Render static React nodes or pass a function
(isActive) => ReactNode
- Render static React nodes or pass a function
- Accessibility
- Custom ARIA attributes via
ariaprop - Automatically applies
aria-current="page"andaria-disabled
- Custom ARIA attributes via
- Test-friendly
data-testidsupport for easy querying in Jest, React Testing Library, Cypress, etc.
- Lightweight & Tree-shakable
- Written in TypeScript, no runtime dependencies beyond React
Using npm:
npm install react-navplusUsing Yarn:
yarn add react-navplusUsing Bun:
bun add react-navplusimport React from 'react';
import { NavPlus } from 'react-navplus';
const Nav = () => (
<nav>
<NavPlus to="/home">Home</NavPlus>
<NavPlus to="/about" matchMode="exact">
About
</NavPlus>
<NavPlus to="https://example.com" isExternal>
External Site
</NavPlus>
</nav>
);If you’re using React Router v6, you can skip passing in location and navigate by using the built-in wrapper:
import React from 'react';
import { RouterNavLink } from 'react-navplus';
const Nav = () => (
<nav>
<RouterNavLink to="/dashboard" activeClassName="active">
Dashboard
</RouterNavLink>
</nav>
);<RouterNavLink
to="/products"
prefetch={{ enabled: true, delay: 150, routerType: 'tanstack-router' }}
>
Products
</RouterNavLink><RouterNavLink
to="/settings"
as="button"
triggerEvent="hover"
navigationDelay={300}
>
{(isActive) => (isActive ? <strong>Settings</strong> : <span>Settings</span>)}
</RouterNavLink>| Prop | Type | Default | Description |
|---|---|---|---|
to |
string |
— | Required. Target URL or path. |
children |
ReactNode | (isActive: boolean) => ReactNode |
— | Content inside the link. Can be a React node or a render function that receives isActive. |
location |
{ pathname: string } |
undefined |
Current location (e.g. from useLocation()). If omitted, link is never active. |
navigate |
(to: string, options?: { replace?: boolean }) => void |
undefined |
Navigation function (e.g. from useNavigate()). If omitted, behaves like a plain <a>. |
matchMode |
'exact' | 'startsWith' | 'includes' | 'pattern' |
'includes' |
How to match location.pathname against to (pattern uses matchPattern). |
matchPattern |
RegExp |
undefined |
Custom regex to match against current pathname (only when matchMode="pattern"). |
customActiveUrl |
string |
undefined |
Use a different URL for active detection instead of to. |
isActiveFunc |
(pathname: string, to: string) => boolean |
undefined |
Fully custom active-detection function. |
prefetch |
boolean | PrefetchOptions |
false |
Enable hover-prefetch. See PrefetchOptions below. |
redirection |
boolean |
true |
If false, renders a <span> and no navigation occurs. |
replace |
boolean |
false |
If true, navigation uses history.replace instead of push. |
navigationDelay |
number (ms) |
undefined |
Delay before performing navigation (useful for animations). |
triggerEvent |
'click' | 'hover' |
'click' |
Which event triggers navigation. |
isExternal |
boolean |
false |
Render as external link (<a target="_blank" rel="noopener noreferrer">). |
disabled |
boolean |
false |
Render as a disabled <span> with aria-disabled. |
as |
React.ElementType |
undefined |
Custom element or component to render instead of <Link>/<a>/<span>. |
className |
string |
'' |
Base class(es) applied to the link. |
activeClassName |
string |
'active' |
Class applied when link is active. |
inActiveClassName |
string |
'' |
Class applied when link is not active. |
activeStyle |
React.CSSProperties |
undefined |
Inline style when active. |
inactiveStyle |
React.CSSProperties |
undefined |
Inline style when inactive. |
id |
string |
undefined |
id attribute on the rendered element. |
aria |
React.AriaAttributes |
{} |
Additional ARIA attributes. |
testId |
string |
undefined |
data-testid for automated tests. |
linkProps |
Omit<LinkProps, 'to' | 'replace'> |
{} |
Extra props to pass down when using React Router’s <Link>. |
routerContext |
any |
undefined |
Pass in your own router context ({ navigate, router }) for custom integrations. |
interface PrefetchOptions {
enabled?: boolean; // default: true
delay?: number; // ms, default: 200
routerType?: 'react-router' | 'tanstack-router' | 'wouter' | 'custom'; // default: 'react-router'
customPrefetch?: (to: string) => void;
}-
Custom matching: Use
matchMode="pattern" matchPattern={/^\/blog(\/|$)/}
for advanced route matching.
-
Custom elements: Pass
as="button"or your own styled component to match your design system. -
Global context: The optional
NavContextinsrc/context/NavContext.tsxcan share navigation state or prefetch logic. -
Standalone hooks: We expose
useIsActiveandusePrefetchinsrc/hooks/if you need the logic outside of the component.
- Fork the repo
- Create your feature branch (
git checkout -b feature/foo) - Commit your changes (
git commit -am 'Add foo') - Push to the branch (
git push origin feature/foo) - Open a Pull Request
Please run npm test and npm run lint before submitting.
MIT © [Your Name or Organization] See LICENSE for details.