Tab with Next.js App Router
Using Next.js Parallel Routes to create accessible Tabs and tab panels that are rendered on the server and controlled by the URL.
import "./style.css";
import type { ReactNode } from "react";
import Link from "next/link.js";
import { Tab, TabList, TabPanel, Tabs } from "./tabs.jsx";
export default function Layout(props: { tabs: ReactNode }) {
return (
<main className="main">
<h1 className="heading">Posts</h1>
<p>
Check out the{" "}
<Link href="/previews/tab-next-router" className="link">
trending posts
</Link>{" "}
or stay up to date with the{" "}
<Link href="/previews/tab-next-router/new" className="link">
latest posts
</Link>
</p>
<div className="wrapper">
<Tabs>
<TabList>
<Tab href="/previews/tab-next-router">Hot</Tab>
<Tab href="/previews/tab-next-router/new">New</Tab>
</TabList>
<TabPanel>{props.tabs}</TabPanel>
</Tabs>
</div>
</main>
);
}
Abstracting the Tab components
In this example, we're abstracting the Ariakit Tab components into higher-level components with a simpler API integrated with the Next.js App Router. Check out the tabs.tsx
file above to see the source code.
We're using React Context to provide the tab store to the TabList
and TabPanel
components. You can learn more about this pattern on the Component stores guide.
Controlling the Tab state
To control the selected tab state, you can pass the selectedId
and setSelectedId
props to useTabStore
. These props allow you to synchronize the tab state with other state sources, such as the browser history.
const router = useRouter();
const pathname = usePathname();
const tab = Ariakit.useTabStore({
selectedId: pathname,
setSelectedId(id) {
router.push(id || pathname);
},
});
You can learn more about controlled state on the Component stores guide.
Rendering a single TabPanel
It's possible to render a single TabPanel
component and use the tabId
prop to point to the selected tab.
const tabId = tab.useState("selectedId");
<TabPanel tabId={tabId}>{props.children}</TabPanel>;