Handbook
Routing
How the links of ferry-ui work with React Router, Next.js, TanStack Router or plain anchors.
Handbook
How the links of ferry-ui work with React Router, Next.js, TanStack Router or plain anchors.
ferry-ui does not import a router. A component that opens a page accepts an href string. With no setup, it renders a plain <a> element.
An adapter is a component that renders the link of your router. It gets href and the attributes of an anchor. It must give all these props to the link.
Define the adapter at module level, not in a component.
import { Link } from 'react-router'
import type { LinkComponent } from 'ferry-ui'
export const RouterLink: LinkComponent = ({ href, ...props }) => <Link to={href} {...props} />import NextLink from 'next/link'
import type { LinkComponent } from 'ferry-ui'
export const RouterLink: LinkComponent = ({ href, ...props }) => <NextLink href={href} {...props} />import { Link } from '@tanstack/react-router'
import type { LinkComponent } from 'ferry-ui'
export const RouterLink: LinkComponent = ({ href, ...props }) => <Link to={href} {...props} />ferry-ui gives a plain string to the adapter. If your router has typed routes, cast href in the adapter.
Give the adapter to LinkProvider, one time, at the root of the app. All the links of ferry-ui below it then go through your router.
import type { ReactNode } from 'react'
import { LinkProvider } from 'ferry-ui'
import { RouterLink } from './router-link'
export function Providers({ children }: { children: ReactNode }) {
return <LinkProvider component={RouterLink}>{children}</LinkProvider>
}ferry-ui does not know the current page. Your router knows it. Set active on the navigation item of the current page.
// `pathname` comes from your router.
const items: NavItem[] = [
{ id: 'projects', label: 'Projects', href: '/projects', active: pathname.startsWith('/projects') },
{ id: 'members', label: 'Members', href: '/members', active: pathname.startsWith('/members') },
]In this demo, a small context does the work of the router. The adapter sends each click to it.
Location of the router: #members
import * as React from 'react'
import { InnerMenu, LinkProvider, type LinkComponent, type NavGroup } from 'ferry-ui'
// This context does the work of a router in the demo: a click changes the location.
const NavigateContext = React.createContext<(href: string) => void>(() => {})
// The adapter. In an app, it renders the link of the router: <Link to={href} {...props} />.
const DemoLink: LinkComponent = ({ href, onClick, ...props }) => {
const navigate = React.useContext(NavigateContext)
return (
<a
href={href}
{...props}
onClick={(event) => {
onClick?.(event)
event.preventDefault()
navigate(href)
}}
/>
)
}
const PAGES = [
{ id: 'profile', label: 'Profile', href: '#profile' },
{ id: 'members', label: 'Members', href: '#members' },
{ id: 'billing', label: 'Billing', href: '#billing' },
]
export default function ActiveFromRouter() {
const [location, setLocation] = React.useState('#members')
// The location of the router gives the active item.
const groups: NavGroup[] = [
{ id: 'settings', items: PAGES.map((page) => ({ ...page, active: page.href === location })) },
]
return (
<NavigateContext.Provider value={setLocation}>
<LinkProvider component={DemoLink}>
<div className="flex w-full max-w-md overflow-hidden rounded-lg border">
<InnerMenu groups={groups} label="Settings" mobileTabs={false} className="w-44 xl:w-44" />
<p className="p-5 text-[13px] text-foreground-light">
Location of the router: <code className="text-foreground">{location}</code>
</p>
</div>
</LinkProvider>
</NavigateContext.Provider>
)
}| Area | Components |
|---|---|
| Navigation | IconRail, IconRailItem, MobileNav, InnerMenu, CommandMenu, ResourceSwitcher |
| Top bar | TopBarLogo, TopBarSegment, TopBarIconButton |
| Page | BreadcrumbLink, PageBackLink |
| Content | ResourceCard, DescriptionItem |
Each of these components also has a linkComponent prop. It replaces the adapter of the provider for this component only.
A navigation item with external: true opens a new tab. It does not use the adapter. It renders a plain <a> element with target="_blank" and rel="noreferrer".
{ id: 'docs', label: 'Documentation', href: 'https://example.com/docs', external: true }href to the component.asChild on Button.<Button asChild>
<Link to="/projects/new">New project</Link>
</Button>useLinkComponent() hook.asChild.