Client Side Routing
Many React Spectrum components support rendering as HTML links. This page discusses how to set up your app to integrate React Spectrum links with your framework or client side router.
Introduction#
React Spectrum components such as Menu, Tabs, TableView, and many others support rendering elements as links that perform navigation when the user interacts with them. Each component that supports link behavior accepts the href
prop, which causes the component to render an <a>
element. Other link DOM props such as target
and download
are also supported.
By default, links perform native browser navigation when they are interacted with. However, many apps and frameworks use client side routers to avoid a full page reload when navigating between pages. The Provider component can configure all React Spectrum components within it to navigate using the client side router you provide. Set this up once in the root of your app, and any React Spectrum component with the href
prop will automatically navigate using your router.
Note that external links to different origins will not trigger client side routing, and will use native browser navigation. Additionally, if the link has a target other than "_self"
, uses the download attribute, or the user presses modifier keys such as Command or Alt to change the default behavior, browser native navigation will occur instead of client side routing.
Provider setup#
The Provider
component accepts a router
prop, which can be set to a router object. This should include a navigate
function received from your router for performing a client side navigation programmatically. The following example shows the general pattern. Framework-specific examples are shown below.
import {Provider, defaultTheme} from '@adobe/react-spectrum';
function App() {
let navigate = useNavigateFromYourRouter();
return (
<Provider theme={defaultTheme} router={{navigate}}>
{/* ... */}
</Provider>
);
}
import {
defaultTheme,
Provider
} from '@adobe/react-spectrum';
function App() {
let navigate = useNavigateFromYourRouter();
return (
<Provider theme={defaultTheme} router={{ navigate }}>
{/* ... */}
</Provider>
);
}
import {
defaultTheme,
Provider
} from '@adobe/react-spectrum';
function App() {
let navigate =
useNavigateFromYourRouter();
return (
<Provider
theme={defaultTheme}
router={{
navigate
}}
>
{/* ... */}
</Provider>
);
}
React Router#
The useNavigate hook from react-router-dom
returns a navigate
function you can pass to Provider
. Ensure that the component that calls useNavigate
and renders Provider
is inside the router component (e.g. BrowserRouter
) so that it has access to React Router's internal context. The React Router <Routes>
element should also be defined inside React Spectrum's <Provider>
so that links inside the rendered routes have access to the router.
import {BrowserRouter, useNavigate} from 'react-router-dom';
import {Provider, defaultTheme} from '@adobe/react-spectrum';
function App() {
let navigate = useNavigate();
return (
<Provider theme={defaultTheme} router={{navigate}}>
{/* Your app here... */}
<Routes>
<Route path="/" element={<HomePage />} />
{/* ... */}
</Routes>
</Provider>
);
}
<BrowserRouter>
<App />
</BrowserRouter>
import {BrowserRouter, useNavigate} from 'react-router-dom';
import {
defaultTheme,
Provider
} from '@adobe/react-spectrum';
function App() {
let navigate = useNavigate();
return (
<Provider theme={defaultTheme} router={{ navigate }}>
{/* Your app here... */}
<Routes>
<Route path="/" element={<HomePage />} />
{/* ... */}
</Routes>
</Provider>
);
}
<BrowserRouter>
<App />
</BrowserRouter>
import {
BrowserRouter,
useNavigate
} from 'react-router-dom';
import {
defaultTheme,
Provider
} from '@adobe/react-spectrum';
function App() {
let navigate =
useNavigate();
return (
<Provider
theme={defaultTheme}
router={{
navigate
}}
>
{/* Your app here... */}
<Routes>
<Route
path="/"
element={
<HomePage />
}
/>
{/* ... */}
</Routes>
</Provider>
);
}
<BrowserRouter>
<App />
</BrowserRouter>
Next.js#
App router#
The useRouter hook from next/navigation
returns a router object that can be used to perform navigation. Provider
should be rendered from a client component at the root of each page or layout that includes React Spectrum components. You can create a new client component for this, or combine it with other top-level providers as described in the Next.js docs.
// app/provider.tsx
"use client";
import {useRouter} from 'next/navigation';
import {Provider, defaultTheme} from '@adobe/react-spectrum';
export function ClientProviders({children}) {
let router = useRouter();
return (
<Provider theme={defaultTheme} router={{navigate: router.push}}>
{children}
</Provider>
);
}
// app/provider.tsx
'use client';
import {useRouter} from 'next/navigation';
import {
defaultTheme,
Provider
} from '@adobe/react-spectrum';
export function ClientProviders({ children }) {
let router = useRouter();
return (
<Provider
theme={defaultTheme}
router={{ navigate: router.push }}
>
{children}
</Provider>
);
}
// app/provider.tsx
'use client';
import {useRouter} from 'next/navigation';
import {
defaultTheme,
Provider
} from '@adobe/react-spectrum';
export function ClientProviders(
{ children }
) {
let router =
useRouter();
return (
<Provider
theme={defaultTheme}
router={{
navigate:
router.push
}}
>
{children}
</Provider>
);
}
Then, in your page or layout server component, wrap your app in the ClientProviders
component that you defined.
// app/layout.tsx
import {ClientProviders} from './provider';
export default function RootLayout({children}) {
return (
<html>
<body>
<ClientProviders>{children}</ClientProviders>
</body>
</html>
);
}
// app/layout.tsx
import {ClientProviders} from './provider';
export default function RootLayout({children}) {
return (
<html>
<body>
<ClientProviders>{children}</ClientProviders>
</body>
</html>
);
}
// app/layout.tsx
import {ClientProviders} from './provider';
export default function RootLayout(
{ children }
) {
return (
<html>
<body>
<ClientProviders>
{children}
</ClientProviders>
</body>
</html>
);
}
Pages router#
The useRouter hook from next/router
returns a router object that can be used to perform navigation. Provider
should be rendered at the root of each page that includes React Spectrum components, or in pages/_app.tsx
to add it to all pages.
// pages/_app.tsx
import type {AppProps} from 'next/app';
import {useRouter} from 'next/router';
import {Provider, defaultTheme} from '@adobe/react-spectrum';
export default function MyApp({Component, pageProps}: AppProps) {
let router = useRouter();
return (
<Provider theme={defaultTheme} router={{navigate: router.push}}>
<Component {...pageProps} />
</Provider>
);
}
// pages/_app.tsx
import type {AppProps} from 'next/app';
import {useRouter} from 'next/router';
import {
defaultTheme,
Provider
} from '@adobe/react-spectrum';
export default function MyApp(
{ Component, pageProps }: AppProps
) {
let router = useRouter();
return (
<Provider
theme={defaultTheme}
router={{ navigate: router.push }}
>
<Component {...pageProps} />
</Provider>
);
}
// pages/_app.tsx
import type {AppProps} from 'next/app';
import {useRouter} from 'next/router';
import {
defaultTheme,
Provider
} from '@adobe/react-spectrum';
export default function MyApp(
{
Component,
pageProps
}: AppProps
) {
let router =
useRouter();
return (
<Provider
theme={defaultTheme}
router={{
navigate:
router.push
}}
>
<Component
{...pageProps}
/>
</Provider>
);
}
Remix#
Remix uses React Router under the hood, so the same useNavigate hook described above also works in Remix apps. Provider
should be rendered at the root of each page that includes React Spectrum components, or in app/root.tsx
to add it to all pages. See the Remix docs for more details.
// app/root.tsx
import {useNavigate, Outlet} from '@remix-run/react';
import {Provider, defaultTheme} from '@adobe/react-spectrum';
export default function App() {
let navigate = useNavigate();
return (
<html lang="en">
<head>
{/* ... */}
</head>
<body>
<Provider theme={defaultTheme} router={{navigate}}>
<Outlet />
</Provider>
{/* ... */}
</body>
</html>
);
}
// app/root.tsx
import {Outlet, useNavigate} from '@remix-run/react';
import {
defaultTheme,
Provider
} from '@adobe/react-spectrum';
export default function App() {
let navigate = useNavigate();
return (
<html lang="en">
<head>
{/* ... */}
</head>
<body>
<Provider
theme={defaultTheme}
router={{ navigate }}
>
<Outlet />
</Provider>
{/* ... */}
</body>
</html>
);
}
// app/root.tsx
import {
Outlet,
useNavigate
} from '@remix-run/react';
import {
defaultTheme,
Provider
} from '@adobe/react-spectrum';
export default function App() {
let navigate =
useNavigate();
return (
<html lang="en">
<head>
{/* ... */}
</head>
<body>
<Provider
theme={defaultTheme}
router={{
navigate
}}
>
<Outlet />
</Provider>
{/* ... */}
</body>
</html>
);
}