Introduction
React Aria components such as Link, Menu, Tabs, and Table transform elements into clickable links that navigate when clicked. These components utilize the href
prop to render as an <a>
tag, supporting attributes like target
and download
.
User interactions with these links vary by component. For instance, one might use arrow keys to navigate tabs or press enter to open a link within a ComboBox. With the href prop, React Aria facilitates seamless navigation for each component.
Typically, links perform the default browser action when clicked. However, many applications employ client-side routers to prevent full page reloads. The RouterProvider configures React Aria components to integrate with your client-side router. Simply set it up at the root, and any React Aria component with an href will automatically utilize your router.
Note that links to external sites will default to the browser's native navigation, and links not targeting "_self", using the download attribute, or modified with keys like Command or Alt, will also follow the browser's native behavior.
Router Provider
The RouterProvider
component accepts two properties: navigate
and useHref
. Assign navigate
to a function from your routing framework that handles client-side navigation. useHref
is optional and modifies a router-specific href to a standard HTML href, such as by adding a base path. Below are setup examples for various frameworks.
import { RouterProvider } from 'react-aria-components';
import { useNavigate, useHref } from 'your-router';
export default function Layout() {
let navigate = useNavigate();
return (
<RouterProvider navigate={navigate} useHref={useHref}>
{/* ... */}
</RouterProvider>
);
}
Inertia.js
To integrate with Inertia.js, you must first declare it in your .d.ts
file, such as in global.d.ts
.
import { type VisitOptions } from '@inertiajs/core'
import { type AxiosInstance } from 'axios'
import { type route as routeFn } from 'ziggy-js'
declare global {
interface Window {
axios: AxiosInstance
}
let route: typeof routeFn
}
declare module 'react-aria-components' {
interface RouterConfig {
routerOptions: VisitOptions
}
}
Next, execute ziggy:generate
to generate the Ziggy routes in your terminal.
php artisan ziggy:generate
Then, proceed to alias 'ziggy-js' in your vite.config.ts
file.
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [
laravel({
input: 'resources/js/app.tsx',
ssr: 'resources/js/ssr.tsx',
refresh: true,
}),
react(),
],
resolve: {
alias: {
'ziggy-js': resolve('vendor/tightenco/ziggy'),
ui: resolve('resources/js/components/ui/index.ts'), // optional if you want to use simple imports
}
}
});
Now, create the provider file in resources/js/components/providers.tsx
and add the following code:
import { router } from '@inertiajs/react'
import React from 'react'
import { RouterProvider } from 'react-aria-components'
import { ThemeProvider } from './theme-provider'
export function Providers({ children }: { children: React.ReactNode }) {
return (
<RouterProvider navigate={(to, options) => router.visit(to, options as any)}>
<ThemeProvider defaultTheme="system" storageKey="theme">
{children}
</ThemeProvider>
</RouterProvider>
)
}
If you're not sure what is theme-provider, refer to the Theme Provider section. After that, go to your resources/js/app.tsx
you can encapsulate <App/>
within Providers
as follows:
import '../css/app.css'
import './bootstrap'
import { Ziggy } from '@/ziggy'
import { createInertiaApp } from '@inertiajs/react'
import { Providers } from 'components/providers'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'
import { createRoot, hydrateRoot } from 'react-dom/client'
import { useRoute } from 'ziggy-js'
const appName = import.meta.env.VITE_APP_NAME || 'Laravel'
createInertiaApp({
title: (title) => (title ? `${title} / ${appName}` : appName),
resolve: (name) => resolvePageComponent(`./pages/${name}.tsx`, import.meta.glob('./pages/**/*.tsx')),
setup({ el, App, props }) {
// @ts-expect-error
window.route = useRoute(Ziggy as any)
const appElement = (
<Providers>
<App {...props} />
</Providers>
)
if (import.meta.env.SSR) {
hydrateRoot(el, appElement)
return
}
createRoot(el).render(appElement)
},
progress: false
})
I don't know if you need this or not, but if you care about ssr.tsx
, you can add the following code:
import { Ziggy as ziggy } from '@/ziggy'
import { createInertiaApp } from '@inertiajs/react'
import createServer from '@inertiajs/react/server'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'
import ReactDOMServer from 'react-dom/server'
import { route, type RouteName } from 'ziggy-js'
const appName = import.meta.env.VITE_APP_NAME || 'Laravel'
createServer(
(page) =>
createInertiaApp({
page,
render: ReactDOMServer.renderToString,
title: (title) => (title ? `${title} / ${appName}` : appName),
resolve: (name) => resolvePageComponent(`./pages/${name}.tsx`, import.meta.glob('./pages/**/*.tsx')),
setup: ({ App, props }) => {
// @ts-expect-error
global.route<RouteName> = (name, params, absolute) =>
// @ts-expect-error
route(name, params as any, absolute, {
...ziggy,
// @ts-expect-error
location: new URL(page.props.ziggy.location)
})
return <App {...props} />
}
})
)
Next.js
The useRouter
hook from next/navigation
provides a router object for navigation purposes. The RouterProvider
should be implemented in a client component at the root of each page or layout that contains React Aria links. Create a new client component in the app
folder named provider.tsx
for this purpose or combine it with other top-level providers as outlined in the Next.js documentation.
'use client';
import { useRouter } from 'next/navigation';
import { RouterProvider as RouterProviderPrimitive } from 'react-aria-components';
declare module 'react-aria-components' {
interface RouterConfig {
routerOptions: NonNullable<
Parameters<ReturnType<typeof useRouter>['push']>[1]
>;
}
}
export function RouteProvider({ children }) {
let router = useRouter();
return (
<RouterProviderPrimitive navigate={router.push}>
{children}
</RouterProviderPrimitive>
);
}
Then, in app/layout.tsx
or your main layout file, enclose the children components within the ClientProviders
component.
import { RouteProvider } from './provider';
export default function RootLayout({children}) {
return (
<html>
<body>
<RouteProvider>{children}</RouteProvider>
</body>
</html>
);
}
Remix
For Remix, we can use the useNavigate
and useHref
hooks. First, let's create a a file route-provider.tsx
in app/components
folder.
For comprehensive details, consult the Remix documentation.
Then in app/root.tsx
or your main layout file you can use the RouteProvider
component to encompass all pages.
Others
If you are using a different framework or router provider not mentioned above, refer to the React Aria Components Docs for additional information on integrating React Aria components with various routers and frameworks.