Navigation
The Navigation
component can be used to create a list of links to different
pages within the app and generally used within a Sheet or
LayoutNav.
Most of the demos on this page are for presentational purpose only and
will use a FakeLink
with a hard-coded pathname
. A "real-world" example is
available as the navigation for this website and some example layouts.
Simple Example
The Navigation
component accepts a list of items
defining all the routes to
render and a data
prop defining the current pathname
and linkComponent
.
The linkComponent
must accept and forward a ref
and accept an href
.
When an item's href
matches the current pathname
, it will gain the active
styles.
The Navigation
component must be wrapped in a <nav>
with an
accessible label when not using the LayoutNav component.
import { card } from "@react-md/core/card/styles";
import { Navigation } from "@react-md/core/navigation/Navigation";
import { type NavigationItem } from "@react-md/core/navigation/types";
import { useNavigationExpansion } from "@react-md/core/navigation/useNavigationExpansion";
import {
type ReactElement,
type AnchorHTMLAttributes,
forwardRef,
} from "react";
import { Tooltip } from "@react-md/core/tooltip/Tooltip";
import { useTooltip } from "@react-md/core/tooltip/useTooltip";
const items: readonly NavigationItem[] = [
{
type: "route",
href: "/",
children: "Home",
},
{
type: "route",
href: "/route-1",
children: "Route 1",
},
{
type: "route",
href: "/route-2",
children: "Route 2",
},
];
export default function SimpleExample(): ReactElement {
const { data } = useNavigationExpansion({
pathname: "/",
linkComponent: FakeLink,
});
return (
<nav aria-label="Fake Navigation" className={card()}>
<Navigation data={data} items={items} />
</nav>
);
}
export interface FakeLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
href: string;
}
export const FakeLink = forwardRef<HTMLAnchorElement, FakeLinkProps>(
function HashLink(props, _ref) {
const { href, ...remaining } = props;
const { elementProps, tooltipProps } = useTooltip({
...props,
hoverTimeout: 0,
defaultPosition: "right",
});
// do not forward the ref so the link is not scrolled into view for demos
// since it scrolls the entire page instead of a navigation panel
return (
<>
<span {...remaining} {...elementProps} />
<Tooltip {...tooltipProps}>href: {href}</Tooltip>
</>
);
},
);
Adding Icons
Icons can be added before or after the children
by using the beforeAddon
and
afterAddon
props respectively.
import { card } from "@react-md/core/card/styles";
import { Navigation } from "@react-md/core/navigation/Navigation";
import { type NavigationItem } from "@react-md/core/navigation/types";
import { useNavigationExpansion } from "@react-md/core/navigation/useNavigationExpansion";
import FavoriteIcon from "@react-md/material-icons/FavoriteIcon";
import HomeIcon from "@react-md/material-icons/HomeIcon";
import StarIcon from "@react-md/material-icons/StarIcon";
import {
type ReactElement,
type AnchorHTMLAttributes,
forwardRef,
} from "react";
import { Tooltip } from "@react-md/core/tooltip/Tooltip";
import { useTooltip } from "@react-md/core/tooltip/useTooltip";
const items: readonly NavigationItem[] = [
{
type: "route",
href: "/",
children: "Home",
beforeAddon: <HomeIcon />,
},
{
type: "route",
href: "/route-1",
children: "Route 1",
beforeAddon: <StarIcon />,
},
{
type: "route",
href: "/route-2",
children: "Route 2",
beforeAddon: <FavoriteIcon />,
afterAddon: <FavoriteIcon />,
},
];
export default function AddingIconsExample(): ReactElement {
const { data } = useNavigationExpansion({
pathname: "/",
linkComponent: FakeLink,
});
return (
<nav aria-label="Fake Navigation" className={card()}>
<Navigation data={data} items={items} />
</nav>
);
}
export interface FakeLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
href: string;
}
export const FakeLink = forwardRef<HTMLAnchorElement, FakeLinkProps>(
function HashLink(props, _ref) {
const { href, ...remaining } = props;
const { elementProps, tooltipProps } = useTooltip({
...props,
hoverTimeout: 0,
defaultPosition: "right",
});
// do not forward the ref so the link is not scrolled into view for demos
// since it scrolls the entire page instead of a navigation panel
return (
<>
<span {...remaining} {...elementProps} />
<Tooltip {...tooltipProps}>href: {href}</Tooltip>
</>
);
},
);
Adding Dividers and Subheaders
Dividers and subheaders can be rendered by setting the type
to "divider"
or
"subheader"
respectively.
import { card } from "@react-md/core/card/styles";
import { Navigation } from "@react-md/core/navigation/Navigation";
import { type NavigationItem } from "@react-md/core/navigation/types";
import { useNavigationExpansion } from "@react-md/core/navigation/useNavigationExpansion";
import {
type ReactElement,
type AnchorHTMLAttributes,
forwardRef,
} from "react";
import { Tooltip } from "@react-md/core/tooltip/Tooltip";
import { useTooltip } from "@react-md/core/tooltip/useTooltip";
const items: readonly NavigationItem[] = [
{
type: "route",
href: "/",
children: "Home",
},
{
type: "route",
href: "/route-1",
children: "Route 1",
},
{
type: "route",
href: "/route-2",
children: "Route 2",
},
{ type: "divider" },
{ type: "subheader", children: "Subheader 1" },
{
type: "route",
href: "/route-3",
children: "Route 3",
},
{
type: "route",
href: "/route-4",
children: "Route 4",
},
];
export default function AddingDividersAndSubheadersExample(): ReactElement {
const { data } = useNavigationExpansion({
pathname: "/",
linkComponent: FakeLink,
});
return (
<nav aria-label="Fake Navigation" className={card()}>
<Navigation data={data} items={items} />
</nav>
);
}
export interface FakeLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
href: string;
}
export const FakeLink = forwardRef<HTMLAnchorElement, FakeLinkProps>(
function HashLink(props, _ref) {
const { href, ...remaining } = props;
const { elementProps, tooltipProps } = useTooltip({
...props,
hoverTimeout: 0,
defaultPosition: "right",
});
// do not forward the ref so the link is not scrolled into view for demos
// since it scrolls the entire page instead of a navigation panel
return (
<>
<span {...remaining} {...elementProps} />
<Tooltip {...tooltipProps}>href: {href}</Tooltip>
</>
);
},
);
Collapsible Groups
A group of routes can be created by creating an item with type: "group"
. If an
href
is also included in this item, all child routes' href
will be prefixed
with the group href
.
import { card } from "@react-md/core/card/styles";
import { Navigation } from "@react-md/core/navigation/Navigation";
import { type NavigationItem } from "@react-md/core/navigation/types";
import { useNavigationExpansion } from "@react-md/core/navigation/useNavigationExpansion";
import {
type ReactElement,
type AnchorHTMLAttributes,
forwardRef,
} from "react";
import { Tooltip } from "@react-md/core/tooltip/Tooltip";
import { useTooltip } from "@react-md/core/tooltip/useTooltip";
const items: readonly NavigationItem[] = [
{
type: "route",
href: "/",
children: "Home",
},
{
type: "group",
href: "/route-1",
children: "Prefixed Href",
items: [
{
type: "route",
href: "/page-1",
children: "Page 1",
},
{
type: "route",
href: "/page-2",
children: "Page 2",
},
{
type: "route",
href: "/page-3",
children: "Page 3",
},
],
},
{
type: "group",
children: "No Prexed Href",
items: [
{
type: "route",
href: "/page-1",
children: "Page 1",
},
{
type: "route",
href: "/page-2",
children: "Page 2",
},
{
type: "route",
href: "/page-3",
children: "Page 3",
},
],
},
{
type: "group",
href: "/multi",
children: "Multiple Levels",
items: [
{
type: "route",
href: "/page-1",
children: "Page 1",
},
{
type: "group",
href: "/level-2",
children: "Level 2",
items: [
{
type: "route",
href: "/page-1",
children: "Page 1",
},
],
},
],
},
];
export default function CollapsibleGroupsExample(): ReactElement {
const { data } = useNavigationExpansion({
pathname: "/route-1/page-2",
linkComponent: FakeLink,
});
return (
<nav aria-label="Fake Navigation" className={card()}>
<Navigation data={data} items={items} />
</nav>
);
}
export interface FakeLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
href: string;
}
export const FakeLink = forwardRef<HTMLAnchorElement, FakeLinkProps>(
function HashLink(props, _ref) {
const { href, ...remaining } = props;
const { elementProps, tooltipProps } = useTooltip({
...props,
hoverTimeout: 0,
defaultPosition: "right",
});
// do not forward the ref so the link is not scrolled into view for demos
// since it scrolls the entire page instead of a navigation panel
return (
<>
<span {...remaining} {...elementProps} />
<Tooltip {...tooltipProps}>href: {href}</Tooltip>
</>
);
},
);
Routing Libraries and Frameworks
Nextjs Example
It is recommended to create a simple LinkUnstyled
component which can be
customized and used throughout the app:
import Link, { type LinkProps } from "next/link.js";
import { type AnchorHTMLAttributes, forwardRef } from "react";
export interface LinkUnstyledProps
extends LinkProps,
AnchorHTMLAttributes<HTMLAnchorElement> {
href: string;
}
export const LinkUnstyled = forwardRef<HTMLAnchorElement, LinkUnstyledProps>(
function LinkUnstyled(props, ref) {
if (props.href.endsWith(".html")) {
return <a {...props} ref={ref} />;
}
return <Link {...props} ref={ref} />;
}
);
Then use the LinkUnstyled
as the linkComponent
and pass the current
pathname
with the usePathname
hook:
"use client";
import { Navigation } from "@react-md/core/navigation/Navigation";
import { type NavigationItem } from "@react-md/core/navigation/types";
import { useNavigationExpansion } from "@react-md/core/navigation/useNavigationExpansion";
import { usePathname } from "next/navigation.js";
import { type ReactElement } from "react";
import { LinkUnstyled } from "@/components/LinkUnstyled.jsx";
const items: readonly NavigationItem[] = [
{
type: "route",
href: "/",
children: "Home",
},
{
type: "route",
href: "/page-1",
children: "Page 1",
},
];
export function MainNavigation(): ReactElement {
const pathname = usePathname();
const { data } = useNavigationExpansion({
pathname,
linkComponent: LinkUnstyled,
});
return (
<nav aria-label="Navigation">
<Navigation data={data} items={items} />
</nav>
);
}
React Router Example
It is recommended to create a simple LinkUnstyled
component which can be
customized and used throughout the app:
import { Link, type LinkProps } from "react-router-dom";
export interface LinkUnstyledProps extends Omit<LinkProps, "to"> {
href: string;
}
export const LinkUnstyled = forwardRef<HTMLAnchorElement, LinkUnstyledProps>(
function LinkUnstyled(props, ref) {
const { href, ...remaining } = props;
return <Link {...remaining} to={href} ref={ref} />;
}
);
Then use the LinkUnstyled
as the linkComponent
and pass the current
pathname
with the useHref
hook:
import { Navigation } from "@react-md/core/navigation/Navigation";
import { type NavigationItem } from "@react-md/core/navigation/types";
import { type ReactElement } from "react";
import { useHref } from "react-router-dom";
import { LinkUnstyled } from "@/components/LinkUnstyled.jsx";
const items: readonly NavigationItem[] = [
{
type: "route",
href: "/",
children: "Home",
},
{
type: "route",
href: "/page-1",
children: "Page 1",
},
];
export function MainNavigation(): ReactElement {
const pathname = useHref();
return (
<nav aria-label="Navigation">
<Navigation
data={{ pathname, linkComponent: LinkUnstyled }}
items={items}
/>
</nav>
);
}
Disclaimer: I haven't actually tried upgrading to react-router-dom@v6.x.x
yet.