Routing Integration
How to wire bestax-bulma components to a client-side router — React Router, Next.js, TanStack
Router, or any library whose navigation primitive is a component you render as. This page
covers the first-hour questions: making Navbar.Item/Menu.Item navigate, making a
Button navigate, and styling the active route.
All examples use React Router 7 or 8, which import from react-router. In a v6 app, import the
same names from react-router-dom. The Next.js differences are at the end.
To start from these patterns, npm create bestax@latest my-app -- -t react-router-ts scaffolds a
Vite app on React Router 8 with a shared navbar and two pages, wired the way this page describes.
Menu items as router links
Menu.Item supports routers out of the box — pass the router's link component via as, and
to (or any other router prop) is forwarded to it. The props follow as, so to is checked
against the router's own types and no cast is needed:
import { Link } from 'react-router';
import { Menu } from '@allxsmith/bestax-bulma';
<Menu>
<Menu.Label>General</Menu.Label>
<Menu.List>
<Menu.Item as={Link} to="/dashboard" active>
Dashboard
</Menu.Item>
<Menu.Item as={Link} to="/customers">
Customers
</Menu.Item>
</Menu.List>
</Menu>;
Navbar items as router links
Navbar.Item works the same way — pass the router's link component via as, and to (or
any other router prop) is forwarded to it. Like Menu.Item, its props follow as, so this
compiles without a cast:
import { Link } from 'react-router';
import { Navbar } from '@allxsmith/bestax-bulma';
<Navbar color="dark">
<Navbar.Menu active={open}>
<Navbar.Start>
<Navbar.Item as={Link} to="/">
Home
</Navbar.Item>
<Navbar.Item as={Link} to="/pricing">
Pricing
</Navbar.Item>
</Navbar.Start>
</Navbar.Menu>
</Navbar>;
Buttons that navigate
Button's as prop is polymorphic (any React.ElementType), so the same two options apply:
import { Link, useNavigate } from 'react-router';
import { Button } from '@allxsmith/bestax-bulma';
// Option 1 — render the Button AS the router link:
<Button as={Link} to="/signup" color="primary" size="large">
Get started
</Button>;
// Option 2 — keep a real <button> and navigate imperatively:
function DemoButton() {
const navigate = useNavigate();
return (
<Button color="primary" onClick={() => navigate('/demo')}>
Live demo
</Button>
);
}
Prefer Option 1 for real navigation (it renders an anchor — right-click, middle-click, and
crawlers work); use Option 2 when navigation is a side effect of app logic (after a form
submit, a confirmation). For plain external URLs, no router is needed:
<Button as="a" href="https://example.com">Docs</Button>.
Active-route styling
Navbar.Item and Menu.Item both take an active boolean — drive it from the router.
Menu.Item also sets aria-current="page" when it's active, so assistive tech hears which item is
current. Navbar.Item only adds is-active, so pass aria-current on it too, as the
react-router starter's navbar does:
<Navbar.Item
as={Link}
to="/about"
active={pathname === '/about'}
aria-current={pathname === '/about' ? 'page' : undefined}
>
About
</Navbar.Item>
The same with Menu.Item, which needs no aria-current of its own:
import { Link, useLocation } from 'react-router';
function SideNav() {
const { pathname } = useLocation();
return (
<Menu>
<Menu.List>
<Menu.Item as={Link} to="/dashboard" active={pathname === '/dashboard'}>
Dashboard
</Menu.Item>
<Menu.Item
as={Link}
to="/customers"
active={
pathname === '/customers' || pathname.startsWith('/customers/')
}
>
Customers
</Menu.Item>
</Menu.List>
</Menu>
);
}
React Router's NavLink also works via as — but its function-style className/children
props bypass the components' own class handling, so the explicit active prop with
useLocation is the pattern that keeps Bulma's is-active styling.
TanStack Router
TanStack Router's Link checks to against your routes, and that check is the reason to choose
it. Rendered through as, its props follow as like any other link, so <Navbar.Item as={Link} to="/about"> compiles and navigates. But the check is gone: a to naming no route compiles too.
Make the component a TanStack link with createLink instead, and to is checked again:
import type { ComponentProps } from 'react';
import { createLink } from '@tanstack/react-router';
import { Navbar } from '@allxsmith/bestax-bulma';
const NavbarLink = createLink(
(props: ComponentProps<typeof Navbar.Item<'a'>>) => <Navbar.Item {...props} />
);
<NavbarLink to="/about" activeProps={{ active: true }}>
About
</NavbarLink>;
activeProps is how TanStack styles the active link, and { active: true } passes bestax's own
active, so the item gets is-active with any class prefix applied. TanStack adds
aria-current="page" to the active link itself, so there's nothing to pass for that. A link to
/ is active on every page unless you give it activeOptions={{ exact: true }}.
npm create bestax@latest my-app -- -t tanstack-router-ts scaffolds a Vite app with this navbar,
code-based routes and two pages.
Next.js
Modern Next.js <Link> (13+) renders the anchor itself and uses href — which Navbar.Item
accepts because its props follow as, so no alias is needed:
import NextLink from 'next/link';
<Navbar.Item as={NextLink} href="/pricing">
Pricing
</Navbar.Item>;
Version notes: Next.js 13–15 also still accepted the deprecated legacyBehavior/passHref
props; Next.js 16 removes them, so the as={NextLink} pattern above is the way forward.
On Next.js before 13, <Link> required an <a> child by default — wrap instead:
<NextLink href="/pricing" passHref>
<Navbar.Item as="a">Pricing</Navbar.Item>
</NextLink>