Skip to content

SideNavigation

Github icon-storybook-square Storybook Markdown Copy for LLM

A side navigation recipe that composes Panel and SidebarNav into an application shell.

Import

import { SideNavigation } from "@cfa/react-core";

Guidelines

When to use

  • Side Navigation is for top-level app destinations.

Do

  • Use short, parallel destination names, one or two words.
  • Mark the current destination with aria-current="page".

Avoid

  • Using Side Navigation for subsections or in-page navigation.
  • Using Side Navigation for a temporary, task-scoped panel. Use Drawer.
  • Using deep multi-level trees. Split sub-areas into page content instead.

Usage

Use SideNavigation as an opinionated, pre-composed application shell. It owns the Panel and SidebarNav composition, maps item data into navigation content, and places children in the adjacent content area. Place SideNavigation.Trigger in the content area so the panel can be reopened after it closes.

View interactive examples in Storybook.

import { SideNavigation } from "@cfa/react-core";
import { Home, Settings } from "@cfa/icons/system";
function DataDrivenSideNavigation() {
const [current, setCurrent] = React.useState("home");
const items = [
{
type: "link",
id: "home",
href: "#home",
label: "Home",
icon: <Home size="xs" />,
},
{
type: "section",
id: "settings",
label: "Settings",
icon: <Settings size="xs" />,
items: [
{ id: "general", href: "#general", label: "General" },
{ id: "profile", href: "#profile", label: "Profile" },
],
},
];
return (
<SideNavigation
layout="contained"
disableKeyboardShortcut
aria-label="Application navigation"
header="Application"
items={items}
currentKey={current}
onItemPress={(key) => setCurrent(String(key))}
contentAs="section"
contentAriaLabel="Example content"
>
<div style={{ padding: 16 }}>
<SideNavigation.Trigger aria-label="Toggle side navigation" />
<h2>Current page: {current}</h2>
<p>This content renders in the inset next to the side navigation.</p>
</div>
</SideNavigation>
);
}

Pass a footer node for content below the navigation list. The UserInfo recipe is a common choice for signed-in user summaries.

import { UserInfo } from "@cfa/react-core";
<SideNavigation
aria-label="Application navigation"
items={items}
footer={<UserInfo name="Truett Cathy" email="[email protected]" initials="TC" />}
>
{children}
</SideNavigation>;

Data Model

Each entry in items uses an explicit type field. Top-level links use type: "link", expandable groups use type: "section", and separators use type: "divider". Nested section links omit type because they are always links. Recipe intentionally supports common navigation fields rather than every underlying Panel or SidebarNav prop.

const items = [
{
type: "link",
id: "home",
href: "/",
label: "Home",
icon: <Home size="xs" />,
},
{
type: "section",
id: "settings",
label: "Settings",
icon: <Settings size="xs" />,
items: [
{ id: "general", href: "/settings/general", label: "General" },
{ id: "profile", href: "/settings/profile", label: "Profile" },
],
},
{ type: "divider", id: "settings-divider" },
{
type: "link",
id: "help",
href: "/help",
label: "Help",
},
];

currentKey marks the current item with aria-current="page". Sections that contain the current item expand automatically. Use onItemPress when the application needs to react to item selection in addition to normal link navigation.

Set closeOnNavigate to close the panel after a link is selected. It defaults to "mobile" so overlay drawers dismiss after navigation on small viewports.

Content Area

The wrapper’s children are rendered in the side navigation content area. Place page content and any external SideNavigation.Trigger there so the panel can be reopened after it closes.

<SideNavigation
aria-label="Application navigation"
header="Application"
items={items}
currentKey={current}
onItemPress={(key) => setCurrent(String(key))}
>
<div style={{ padding: 16 }}>
<SideNavigation.Trigger aria-label="Toggle side navigation" />
<h1>Home</h1>
<p>Main application content.</p>
</div>
</SideNavigation>

For contained layouts nested inside an existing page, use contentAs and contentAriaLabel to avoid adding another page-level main landmark.

<SideNavigation
layout="contained"
disableKeyboardShortcut
aria-label="Dashboard navigation"
items={items}
contentAs="section"
contentAriaLabel="Dashboard content"
>
<DashboardContent />
</SideNavigation>

Opinionated Composition

SideNavigation is intentionally not a recipe namespace for assembling raw Panel and SidebarNav primitives. If an application needs deeper customization than the data-driven API supports, compose Panel and SidebarNav directly instead of routing those concerns through the recipe.