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
isActiveFunc
orcustomActiveUrl
- 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
,inActiveClassName
activeStyle
,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
aria
prop - Automatically applies
aria-current="page"
andaria-disabled
- Custom ARIA attributes via
- Test-friendly
data-testid
support 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-navplus
Using Yarn:
yarn add react-navplus
Using Bun:
bun add react-navplus
import 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
NavContext
insrc/context/NavContext.tsx
can share navigation state or prefetch logic. -
Standalone hooks: We expose
useIsActive
andusePrefetch
insrc/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.