Skip to content
Monogem

Pattern

Navigation

Overview

Monogem ships six separate navigation primitives — Navbar, Sidebar Nav, Bottom Nav, Breadcrumb, Tabs, and Pagination — each documenting its own anatomy, states, and contract in isolation. None of them says how a real product combines two or more of them on the same screen. This is a composition pattern, not a seventh primitive: Monogem ships no <Navigation> component. It is decision guidance — which primitive answers which navigation job, and how the already-documented primitives combine — plus a live example that composes them unchanged.

No change to any of the six primitives — each keeps its documented contract exactly as written. This page cites Navbar, Sidebar Nav, Bottom Nav, Breadcrumb, Tabs, and Pagination; it never redefines them. It also invents no application routing architecture and no product-specific information architecture — “which page is current” and “what the sections are called” stay the caller's decision in every composition below, the same deferral each primitive already makes on its own page.

Anatomy

Every navigation primitive already answers "reach for something else when the job is different" on its own page. This table collects those individual answers in one place:

PrimitiveAnswersScopeChanges the route?
Navbar"How do I get into this site/app?"Global, top-of-page, every width (collapses itself below md)Yes — links navigate
Sidebar Nav"What are this app's primary sections?"App-shell, persistent, desktop/tabletYes — items navigate
Bottom NavSame job as Sidebar Nav, mobile-onlyApp-shell, fixed to the viewport, below md onlyYes — items navigate
Breadcrumb"Where am I in the hierarchy, and how do I step back up?"Secondary, in-page, orientation onlyYes for ancestor links — the current page itself is a non-link <span>
Tabs"Which view of this same page/content?"Same-page, no navigationNo — same URL, same route
Pagination"Which page of this bounded result set?"Same-page-type, list contextUsually — a real page route (?page=3)

Two axes fall out of this table and explain most of the guidance below:

  • Primary vs. secondary. Navbar, Sidebar Nav, and Bottom Nav are the way a user enters or moves between an app's major sections — a product needs at least one. Breadcrumb, Tabs, and Pagination are never a product's only navigation; they supplement whatever primary nav already got the user to the current page — Breadcrumb's own Overview makes this explicit ("it supplements primary nav, it doesn't replace it").
  • Navigates vs. stays put. Navbar, Sidebar Nav, Bottom Nav, Breadcrumb's ancestor links, and Pagination all change what page or route is loaded. Tabs never does — see tabs.md's own "Tabs vs. navigation links" table. It is the one primitive in this set that is not, strictly, navigation at all, which is why reaching for it to build primary site navigation is called out as a misuse on its own page.

Live Example

Global + local navigation, with hierarchy and same-page views

Acme
  • Draft the onboarding checklist
  • Review the Q3 roadmap

Navbar carries no destination links here — Sidebar Nav already owns “which section,” so the top bar stays limited to the brand and an always-visible action. The Breadcrumb trail and the selected Tabs trigger are each their own “current” signal, independent of Sidebar Nav’s current section.

Responsive substitution — rail hands off to a bottom tab bar

Wide (desktop/tablet) — Sidebar Nav rail

Narrow (mobile) — Bottom Nav bar

Both frames render the same four destinations, in the same order, and share one “current” value — activating an item in either frame moves both. This is a static stand-in for the hand-off; in real usage the rail is `md:block` and Bottom Nav is `md:hidden`, so only one is ever actually visible at a given viewport width (Bottom Nav’s own real `fixed` / `md:hidden` classes are also overridden here, the same demo-only override its own live example uses).

Code Example

navigation-showcase.tsx
"use client";

import type { MouseEvent } from "react";
import { useState } from "react";
import { Activity, Compass, FolderKanban, Home, LayoutDashboard, Settings } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Navbar, NavbarActions, NavbarBrand, NavbarContent } from "@/components/ui/navbar";
import {
SidebarNav,
SidebarNavGroup,
SidebarNavItem,
} from "@/components/ui/sidebar-nav";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { Tabs, TabsList, TabsPanel, TabsTrigger } from "@/components/ui/tabs";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
import { BottomNav, BottomNavItem } from "@/components/ui/bottom-nav";

/**
* Live Navigation example — every part is a real, unmodified primitive from
* its own doc; this page adds no components/ui/navigation.tsx of any kind.
* `"use client"` only so the demo can swallow real navigation and hold
* current-page / tab / pagination state — every part rendered is itself a
* server component.
*/
const stayOnPage = (event: MouseEvent<HTMLAnchorElement>) => event.preventDefault();

const TASKS = [
"Draft the onboarding checklist",
"Review the Q3 roadmap",
"Fix the broken invite link",
"Write release notes",
"Pair on the billing migration",
"Triage the support backlog",
];
const TASKS_PER_PAGE = 2;
const TASK_PAGE_COUNT = Math.ceil(TASKS.length / TASKS_PER_PAGE);

/**
* Section 1 — global + local navigation composed with hierarchy (Breadcrumb)
* and same-page views (Tabs), one of which pages a list (Pagination).
* Navbar renders no `NavbarLinks` / `NavbarMobileMenu` here — Sidebar Nav
* already owns "which section," per docs/patterns/navigation.md's "Global
* and local navigation."
*/
function GlobalAndLocalExample() {
const [taskPage, setTaskPage] = useState(1);

const pageTasks = TASKS.slice(
(taskPage - 1) * TASKS_PER_PAGE,
taskPage * TASKS_PER_PAGE,
);

return (
<section className="flex flex-col gap-3">
<h3 className="text-sm font-medium leading-snug text-muted-foreground">
Global + local navigation, with hierarchy and same-page views
</h3>
<div className="overflow-hidden rounded-md border border-solid border-border">
<Navbar className="static">
<NavbarContent>
<NavbarBrand>
<span className="rounded-md px-1 py-1.5 text-sm font-semibold leading-snug tracking-tight">
Acme
</span>
</NavbarBrand>
<NavbarActions>
<Button variant="outline" size="sm">
Account
</Button>
</NavbarActions>
</NavbarContent>
</Navbar>

<div className="flex items-stretch">
<aside className="w-48 shrink-0 bg-sidebar p-3 text-sidebar-foreground">
<SidebarNav aria-label="Product">
<SidebarNavGroup label="Workspace">
<SidebarNavItem
href="/dashboard"
icon={<LayoutDashboard />}
onClick={stayOnPage}
>
Dashboard
</SidebarNavItem>
<SidebarNavItem
href="/projects"
current
icon={<FolderKanban />}
onClick={stayOnPage}
>
Projects
</SidebarNavItem>
<SidebarNavItem
href="/settings"
icon={<Settings />}
onClick={stayOnPage}
>
Settings
</SidebarNavItem>
</SidebarNavGroup>
</SidebarNav>
</aside>

<div className="flex-1 p-4">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/dashboard" onClick={stayOnPage}>
Dashboard
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/projects" onClick={stayOnPage}>
Projects
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Acme redesign</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>

<div className="mt-4">
<Tabs defaultValue="tasks">
<TabsList aria-label="Project views" className="overflow-y-hidden">
<TabsTrigger value="tasks">Tasks</TabsTrigger>
<TabsTrigger value="activity">Activity</TabsTrigger>
</TabsList>
<TabsPanel value="tasks" className="flex flex-col gap-3 pt-3">
<ul className="flex flex-col gap-1">
{pageTasks.map((task) => (
<li
key={task}
className="rounded-sm px-2 py-1.5 text-sm leading-normal text-foreground"
>
{task}
</li>
))}
</ul>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href={`?page=${taskPage - 1}`}
disabled={taskPage === 1}
onClick={(event) => {
event.preventDefault();
setTaskPage((page) => Math.max(1, page - 1));
}}
/>
</PaginationItem>
{Array.from({ length: TASK_PAGE_COUNT }, (_, i) => i + 1).map(
(page) => (
<PaginationItem key={page}>
<PaginationLink
href={`?page=${page}`}
isActive={taskPage === page}
onClick={(event) => {
event.preventDefault();
setTaskPage(page);
}}
>
{page}
</PaginationLink>
</PaginationItem>
),
)}
<PaginationItem>
<PaginationNext
href={`?page=${taskPage + 1}`}
disabled={taskPage === TASK_PAGE_COUNT}
onClick={(event) => {
event.preventDefault();
setTaskPage((page) =>
Math.min(TASK_PAGE_COUNT, page + 1),
);
}}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</TabsPanel>
<TabsPanel
value="activity"
className="pt-3 text-sm leading-normal text-muted-foreground"
>
Recent activity on this project — comments, status changes,
and assignments, newest first.
</TabsPanel>
</Tabs>
</div>
</div>
</div>
</div>
<p className="text-xs leading-snug text-muted-foreground">
Navbar carries no destination links here — Sidebar Nav already owns
“which section,” so the top bar stays limited to the brand and an
always-visible action. The Breadcrumb trail and the selected Tabs
trigger are each their own “current” signal, independent of Sidebar
Nav’s current section.
</p>
</section>
);
}

const SHELL_ITEMS = [
{ href: "/home", label: "Home", icon: <Home /> },
{ href: "/explore", label: "Explore", icon: <Compass /> },
{ href: "/activity", label: "Activity", icon: <Activity /> },
{ href: "/settings", label: "Settings", icon: <Settings /> },
];

/**
* Section 2 — the same app-shell destination list rendered as both forms
* named in docs/patterns/navigation.md's "Desktop-to-mobile transitions":
* a Sidebar Nav rail (wide) and a Bottom Nav bar (narrow). One shared
* `current` state drives both frames, standing in for the real `md:block` /
* `md:hidden` breakpoint hand-off Sidebar Nav's own and Bottom Nav's own
* responsive examples each demonstrate individually against the actual
* viewport.
*/
function ResponsiveSubstitutionExample() {
const [current, setCurrent] = useState(SHELL_ITEMS[0].href);

return (
<section className="flex flex-col gap-3">
<h3 className="text-sm font-medium leading-snug text-muted-foreground">
Responsive substitution — rail hands off to a bottom tab bar
</h3>
<div className="flex flex-wrap gap-6">
<div className="flex flex-col gap-2">
<p className="text-xs leading-snug text-muted-foreground">
Wide (desktop/tablet) — Sidebar Nav rail
</p>
<div className="h-64 w-48 overflow-hidden rounded-md border border-solid border-border bg-sidebar p-3 text-sidebar-foreground">
<SidebarNav aria-label="Product">
<SidebarNavGroup>
{SHELL_ITEMS.map((item) => (
<SidebarNavItem
key={item.href}
href={item.href}
icon={item.icon}
current={item.href === current}
onClick={(event) => {
stayOnPage(event);
setCurrent(item.href);
}}
>
{item.label}
</SidebarNavItem>
))}
</SidebarNavGroup>
</SidebarNav>
</div>
</div>

<div className="flex flex-col gap-2">
<p className="text-xs leading-snug text-muted-foreground">
Narrow (mobile) — Bottom Nav bar
</p>
<div className="relative h-64 w-80 overflow-hidden rounded-md border border-solid border-border bg-background">
<div className="flex h-full flex-col gap-2 p-3 pb-20">
<p className="text-xs leading-snug text-muted-foreground">
Page content
</p>
</div>
<BottomNav className="absolute md:flex">
{SHELL_ITEMS.map((item) => (
<BottomNavItem
key={item.href}
href={item.href}
icon={item.icon}
label={item.label}
current={item.href === current}
onClick={(event) => {
stayOnPage(event);
setCurrent(item.href);
}}
/>
))}
</BottomNav>
</div>
</div>
</div>
<p className="text-xs leading-snug text-muted-foreground">
Both frames render the same four destinations, in the same order, and
share one “current” value — activating an item in either frame moves
both. This is a static stand-in for the hand-off; in real usage the
rail is `md:block` and Bottom Nav is `md:hidden`, so only one is ever
actually visible at a given viewport width (Bottom Nav’s own real
`fixed` / `md:hidden` classes are also overridden here, the same
demo-only override its own live example uses).
</p>
</section>
);
}

export function NavigationShowcase() {
return (
<div className="flex flex-col gap-10">
<GlobalAndLocalExample />
<ResponsiveSubstitutionExample />
</div>
);
}

Usage Guidance

Choosing a primitive

  • A user has just arrived, or needs to reach the app/site at all → Navbar. It is the only primitive here designed to also work as a marketing/site-wide header (brand + links + actions), and the only one that bakes in its own full mobile collapse (hamburger + Sheet) rather than asking the caller to compose one.
  • The user is already inside an app and needs to move between its primary sections → Sidebar Nav on desktop/tablet, Bottom Nav on mobile — see Desktop-to-mobile transitions for how the two relate. Reach for Sidebar Nav specifically when destinations benefit from grouping (its own SidebarNavGroup); reach for Bottom Nav specifically when the product is mobile/app-style and destinations fit 3–5 thumb-reachable items (bottom-nav.md's own "Do" guidance).
  • The user needs to know where the current page sits in a hierarchy, or step back up it → Breadcrumb. Only worth adding when nesting is more than one level deep and stable — skip it for a flat site or a single-level hierarchy, per breadcrumb.md's own Overview.
  • The user is switching between equal-standing views of the same subject, with no route change → Tabs. If the destinations are genuinely different pages, that's navigation links (possibly inside a Navbar or Sidebar Nav), not Tabs.
  • The user is paging through a long, bounded, ordered result set → Pagination. Not for an endless feed (infinite scroll) or a small list that fits on one page, per pagination.md's own Overview.

Common compositions

Global and local navigation

A product with both a site-wide shell (marketing pages, sign-in, account switching) and an app-shell with its own sections composes Navbar for the former and Sidebar Nav (or Bottom Nav below md) for the latter — never one primitive trying to do both jobs. The usual split, and the one the Live Example above builds:

  • Navbar stays thin once the user is inside the app — a brand mark linking back to the app root, plus NavbarActions (a theme toggle, an account control). It renders no NavbarLinks in this composition — Sidebar Nav already owns "which section," so a second, competing list of the same destinations in the top bar would give the user two places to look for one decision.
  • Sidebar Nav carries the app's actual primary destinations, grouped where that helps.
  • Both mark "current" independently, from the same caller-computed route match — Navbar has no current link in this split (it has no destination links to mark); Sidebar Nav marks the current section.

A marketing site with no app-shell just uses Navbar alone with its NavbarLinks populated, per navbar.md's own baseline. A single-surface app with no separate marketing shell just uses Sidebar Nav (or Bottom Nav) alone. The "both, split by role" composition above is specifically for a product that is both.

Desktop-to-mobile transitions

The three primary-navigation primitives each handle a narrow viewport differently, by design — this pattern doesn't reconcile them into one behavior, it explains which one to reach for:

PrimitiveBelow mdCaller composition needed?
NavbarBakes in its own collapse — NavbarLinks hides, NavbarMobileMenu (hamburger + Sheet) appears in its place, in the same treeNo — Navbar owns this entirely (see navbar.md's Responsive contract)
Sidebar NavRenders a static list at every width; no built-in breakpointYes — the caller composes a persistent rail (md:block) alongside the same SidebarNav tree rendered inside a Sheet, following Sidebar Nav's own documented shape
Bottom NavBakes in its own visibility — fixed, md:hiddenNo — mount it once at the shell level; it hides itself (see bottom-nav.md's Responsive contract)

The common product pattern that falls out of this table is Sidebar Nav paired with Bottom Nav: the rail renders md:block, Bottom Nav renders itself at the same breakpoint's other side (md:hidden is its own built-in behavior) — together they give an app-shell primary navigation that's present at every width without either primitive changing shape into the other. Bottom Nav's own doc names this explicitly: "A product that needs primary navigation at wider viewports mounts Navbar or Sidebar Nav alongside it." The Live Example's second section demonstrates the rail/bar hand-off side by side.

A Navbar-based marketing shell does not need a Bottom Nav counterpart — Navbar's own hamburger + Sheet is already its complete mobile answer; pairing it with Bottom Nav would give a narrow viewport two competing primary-navigation surfaces — see When they coexist vs replace one another.

Communicating current location

Every primitive in this set that navigates marks "where the user is" the same way — one consistent signal a caller writes once and gets right everywhere:

  • aria-current="page", plus a text-weight or fill change, never color alone — Navbar, Sidebar Nav, Bottom Nav, Breadcrumb's BreadcrumbPage, and Pagination's isActive link all use this exact pair (see each primitive's own States / Accessibility sections). In every case it's the caller's job to compute which item is current (a router match, usePathname) — no primitive in this set does route matching itself.
  • Tabs is the one exception, deliberately so — it isn't marking a page location, it's marking a selected view: aria-selected plus aria-controls / aria-labelledby between the trigger and its panel (see tabs.md's Accessibility), not aria-current. Don't reach for aria-current inside a Tabs composition; it isn't the right signal for a same-page view switch.
  • A page composing several of these together (the Live Example's combined section) ends up with more than one "current" marker on screen at once, each scoped to its own landmark — Sidebar Nav's current section, a Breadcrumb's current page, and a Tabs' selected trigger are three independent, simultaneously-true facts, not competing claims. Each comes from a different piece of state (route section, full path, selected tab value), and each primitive's own landmark (<nav aria-label="…"> or role="tablist") keeps them from being confused with one another by assistive tech.

Hierarchy

Primary navigation (Navbar / Sidebar Nav / Bottom Nav) answers "which section" — a shallow, usually one-level choice. Breadcrumb answers "how deep, and through what path" once the user is inside a section whose own pages nest further. The two never substitute for each other — a Breadcrumb with no primary nav behind it has nothing to be a supplement to (breadcrumb.md's own Overview and Usage sections both make this explicit), and a Sidebar Nav item is not a substitute for a multi-level trail once a section's own pages nest more than one level deep. Composing them together (Sidebar Nav in the shell, Breadcrumb at the top of the content region) is the default shape for a documentation site, a settings area, or a catalog — the shape the Live Example builds.

When they coexist vs replace one another

PairCoexist or replaceWhy
Navbar + Sidebar NavCoexist, different rolesGlobal entry vs. app-local sections — see Global and local navigation
Sidebar Nav + Bottom NavReplace each other by breakpoint, same roleBoth answer "which primary section" — one is the desktop/tablet form, the other the mobile form, of the same decision. Mount both; only one is ever visible at a time.
Navbar + Bottom NavUsually replace, same underlying role, if Navbar is standing in for Sidebar Nav's job below mdNavbar already has a complete built-in mobile answer (its own hamburger + Sheet); adding Bottom Nav alongside it gives a narrow viewport two competing "primary navigation" surfaces. A shell that wants a thumb-reachable tab bar instead of a hamburger on mobile reaches for Bottom Nav in Sidebar Nav's place, not in addition to Navbar's own collapse.
Any primary nav + BreadcrumbCoexist, different rolesPrimary nav picks the section; Breadcrumb shows depth within it — see Hierarchy
Any primary nav + TabsCoexist, different rolesTabs operate inside a page already reached through primary nav; it never picks the page itself
Breadcrumb + TabsCoexist, different axesHierarchy (where nested) vs. same-page views (what's shown) — both can sit on one page, breadcrumb above, tabs below, as in the Live Example
Pagination + anything aboveCoexist, unrelated scopePagination pages through a list inside whatever content region the surrounding nav already brought the user to

The one rule that falls out of every row: don't give a single viewport two primitives competing for the same "which primary section" decision. Everything else in this set answers a distinct enough question that composing it alongside another primitive is the normal, expected shape, not an edge case.

What the example composes

The example below composes every primitive on this page unchanged, in the two shapes named above:

  • Global and local navigation, with hierarchy and same-page views — a thin Navbar (brand + one action, no NavbarLinks) over a Sidebar Nav rail and a content region that carries its own Breadcrumb trail and a Tabs switcher, one of whose panels pages a result list with Pagination.
  • Responsive substitution — the same app-shell destinations rendered twice, side by side: as a Sidebar Nav rail (the wide-viewport form) and as a Bottom Nav bar (the narrow-viewport form), both marking the same destination current — a static side-by-side stand-in for the real md:block / md:hidden breakpoint hand-off Sidebar Nav's own and Bottom Nav's own responsive examples each demonstrate individually.

Spacing

No new spacing values. Layout gaps between composed primitives in the live example are ordinary Spacing rhythm already used elsewhere on the site — --scale-6 between the sidebar rail and the content column, --scale-4 between a Breadcrumb trail and the Tabs below it. Each composed primitive's own internal spacing (a NavbarLink's padding, a SidebarNavItem's row height, a PaginationLink's size-10 footprint) is untouched, defined on its own page.

Tokens

Every value in the Live Example comes from the six primitives' own already-documented token families, unchanged:

FamilyWhereDoc
--background / --border / --muted-foreground / --foreground / --ringNavbar's chrome and link statesnavbar.md
--sidebar / --sidebar-foreground / --sidebar-primary / --sidebar-accent / --sidebar-ringSidebar Nav's surface and item statessidebar-nav.md
--background / --border / --muted-foreground / --foreground / --ringBottom Nav's chrome and item statesbottom-nav.md
--muted-foreground / --foreground / --ringBreadcrumb's link and current-page statesbreadcrumb.md
--foreground / --muted-foreground / --primary / --border / --ringTabs' selected/unselected trigger statestabs.md
--accent / --accent-foreground / --border / --background / --foreground / --ringPagination's link states, via buttonVariantspagination.md

Do / Don’t

Do

  • Pick exactly one primitive per navigation role on a given screen — one "which section" answer, one "where in the hierarchy" answer, and so on — per When they coexist vs replace one another.
  • Give Sidebar Nav and Bottom Nav the same destination list, in the same order, when a product pairs them across breakpoints — the same "one destination list rendered twice, not two information architectures" rule Navbar's own Usage section states for its own desktop/mobile split.
  • Compute "current" independently for each composed primitive, from the same underlying route state, and let each mark it with its own documented aria-current / aria-selected treatment — see Communicating current location.
  • Let Breadcrumb and Tabs coexist freely with primary nav and with each other — they answer different questions, and neither owns "which primary section."
  • Keep this page's guidance to composition — for a single primitive's own anatomy, states, or accessibility contract, defer to that primitive's own doc; don't re-derive it here.

Don’t

  • Build a <Navigation> wrapper around two or more of these primitives — the same rejection Search and Empty States make for their own wrapper components.
  • Render two primitives that both claim to answer "which primary section" at the same viewport width (e.g. a populated NavbarLinks and a visible Bottom Nav at once) — see When they coexist vs replace one another.
  • Reach for Tabs to build primary site navigation, or Breadcrumb as a site's only navigation — both are explicitly called out as misuse on their own pages.
  • Invent an active-route matcher, a notification/badge data source, or any other product-specific information architecture at this layer — every primitive in this set already defers "which item is current" and similar state to the caller; this pattern page doesn't override that deferral.
  • Duplicate a primitive's documented contract here instead of linking to it — if a detail isn't specific to composing two primitives together, it belongs on that primitive's own page, not this one.

Accessibility

  • Multiple <nav> landmarks on one screen need distinct names. Navbar, Sidebar Nav, Bottom Nav, Breadcrumb, and Pagination each render their own <nav> with a sensible default aria-label ("Primary", "Sidebar", "Breadcrumb", "Pagination") — when a composition like Global and local navigation puts more than one on screen at once, give each a distinct label (every primitive's own doc already documents this override) so a screen-reader user's landmark list distinguishes "jump to primary navigation" from "jump to the breadcrumb."
  • aria-current stays singular per landmark, never global. Each <nav> in a composition marks its own current item independently — Sidebar Nav's current section and Breadcrumb's current page are two separate aria-current="page" occurrences in two different landmarks, not a conflict; the "exactly one per nav" rule stated on every primary-nav and Breadcrumb/Pagination page applies per landmark, not per page.
  • Tabs' roving-tabindex model is self-contained. A Tabs instance inside a larger navigation composition keeps its own Tab / arrow-key contract exactly as documented — composing it alongside Sidebar Nav or Breadcrumb (both plain lists of links, no roving tabindex) doesn't change either model; they simply sit at different points in the page's natural tab order.
  • Sheet-based mobile menus (Navbar's built-in one, or a caller-composed Sidebar Nav drawer) keep their own focus-trap and restoration contract unchanged — inherited from Sheet, not something this pattern adds to or modifies.
  • Contrast. Every token pairing cited in Tokens used already meets WCAG AA on its own primitive's page; composing primitives together changes no color pairing.

Related Components / Patterns

  • Navbar — global, top-of-page navigation.
  • Sidebar Nav — app-shell navigation on wider viewports.
  • Bottom Nav — the mobile-only counterpart.
  • Breadcrumb — orientation within a hierarchy.
  • Tabs — views on the same page.
  • Pagination — moving through a result set.