Skip to content
design
Components

Navigation

Choose patterns for application destinations, page hierarchy, peer views, collections and task progress.

On this page
Report library

Engineering reviews, daily updates and feature reports.

Engineering review

Illustrative entry from page 1

Daily update

Illustrative entry from page 1

Feature report

Illustrative entry from page 1

All reports, page 1.

Several navigation patterns share a report-library view, each with a distinct job.
Code
import { Breadcrumbs } from "@bosunkit/components/breadcrumbs";
import { Button } from "@bosunkit/components/button";
import { CommandPalette, type CommandGroup, type CommandItem } from "@bosunkit/components/command-palette";
import { Pagination, type PaginationLabels } from "@bosunkit/components/pagination";
import { Tabs } from "@bosunkit/components/tabs";
import { Search } from "lucide-react";
import { useId, useState, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const description: string =
  "A report-library composition: breadcrumbs identify location, tabs change the collection, pagination changes the visible page, and the command menu offers a direct route. All results are illustrative.";
const labels: PaginationLabels = {
  previous: "Previous library page",
  next: "Next library page",
  first: "First library page",
  last: "Last library page",
  page: (page: number): string => `Library page ${String(page)}`,
  summary: (page: number, total: number): string => `Page ${String(page)} of ${String(total)}`,
};
const groups: readonly CommandGroup[] = [
  {
    id: "library",
    label: "Report library",
    items: [
      { id: "all", label: "Show all reports" },
      { id: "saved", label: "Show saved reports" },
    ],
  },
];
export default function Showcase(): ReactElement {
  const [tab, setTab]: [string, Dispatch<SetStateAction<string>>] = useState("all");
  const [page, setPage]: [number, Dispatch<SetStateAction<number>>] = useState(1);
  const [open, setOpen]: [boolean, Dispatch<SetStateAction<boolean>>] = useState(false);
  const resultId: string = useId();
  function choose(value: string): void {
    setTab(value);
    setPage(1);
  }
  return (
    <div style={{ display: "grid", gap: "var(--space-lg)", width: "100%" }}>
      <Breadcrumbs
        label="Library hierarchy"
        expandLabel="Show library ancestors"
        collapseLabel="Hide library ancestors"
        items={[
          { id: "design", label: "Design", href: "/" },
          { id: "components", label: "Components", href: "/docs/components" },
          { id: "library", label: "Report library example" },
        ]}
      />
      <div
        style={{
          display: "flex",
          flexWrap: "wrap",
          justifyContent: "space-between",
          alignItems: "center",
          gap: "var(--space-md)",
        }}
      >
        <strong>Report library</strong>
        <Button
          variant="ghost"
          onClick={(): void => {
            setOpen(true);
          }}
        >
          <Search size="1em" aria-hidden="true" />
          Find a collection
        </Button>
      </div>
      <Tabs
        tabs={[
          { id: "all", label: "All reports" },
          { id: "saved", label: "Saved reports" },
        ]}
        value={tab}
        onValueChange={choose}
      >
        <Tabs.Panel value="all">
          <p>Engineering reviews, daily updates and feature reports.</p>
        </Tabs.Panel>
        <Tabs.Panel value="saved">
          <p>Your selected reading list in this local example.</p>
        </Tabs.Panel>
      </Tabs>
      <div id={resultId} style={{ display: "grid", gap: "var(--space-sm)" }}>
        {["Engineering review", "Daily update", "Feature report"].map((label: string): ReactElement => (
          <div
            key={label}
            style={{ padding: "var(--space-md)", background: "var(--surface2)", borderRadius: "var(--radius-md)" }}
          >
            <strong>{label}</strong>
            <p style={{ margin: 0, color: "var(--text-dim)" }}>
              Illustrative {tab === "saved" ? "saved " : ""}entry from page {String(page)}
            </p>
          </div>
        ))}
      </div>
      <Pagination
        label="Library results"
        pageCount={tab === "all" ? 12 : 3}
        value={page}
        onValueChange={setPage}
        labels={labels}
        controls={resultId}
      />
      <p role="status">
        {tab === "all" ? "All reports" : "Saved reports"}, page {String(page)}.
      </p>
      <CommandPalette
        open={open}
        onOpenChange={setOpen}
        title="Find a collection"
        closeLabel="Close collection finder"
        searchLabel="Search collections"
        clearLabel="Clear collection search"
        emptyText="No matching collections."
        loadingText="Loading collections…"
        groups={groups}
        onSelect={(item: CommandItem): void => {
          choose(item.id);
        }}
      />
    </div>
  );
}

Navigation helps a reader locate the current page, choose another destination and understand what a control will change. Combine patterns by purpose and keep their state tied to the route or task that owns it.

01Separate location from selectionA destination is a link, a local view is a tab, and a workflow stage is a controlled navigation request.
02Make the current context visibleUse the correct current-page, selected-tab or current-step semantics, with a visible indicator that survives theme changes.
03Preserve the way backKeep route state in URLs, drafts with their workflow, and focus connected to the control or content the user chose.

Choose a pattern

NeedComponentState owner
Persistent destinations with groups and a panel/rail formNavigation listRouter supplies the current href; shell owns column size
A small set of primary destinations on a phoneNavigation barRouter supplies the current destination; shell owns More
Ancestors of the current pageBreadcrumbsRouter or content hierarchy supplies the path
Peer views within one subjectTabsCaller owns active view and its panels
Pages of a bounded collectionPaginationURL or collection controller owns page, filters and requests
Named stages in a taskStepsWorkflow owns validation, completion and draft answers
Direct lookup of a destination or actionCommand paletteShell owns opening; router/action layer owns the selected command

A single screen may use several of these. In the specimen, breadcrumbs show hierarchy, tabs choose a collection, pagination changes the visible slice, and a command menu can select that collection directly. Side navigation and a phone tab bar belong in the App shell, surrounding the page content.

Primary, secondary and content regions

Use one persistent column for application destinations. A second column can select an object within the current area: a team, folder or project. Breadcrumbs then locate that object, and tabs switch views of the same object. The workspace example combines these regions with a bottom account menu and a wide desktop preview.

Give rows a consistent target and a small inter-row gap. Use a larger gap between labeled destination groups. Keep a shared inset on each side of a column divider, with more space before reading content than between list items. The navigation tokens name group, row, column and content spacing separately. A wider column should reveal more of a label, without enlarging the type or stretching row height.

Account identity belongs at the foot of the primary column. NavUser exposes a named menu with caller-owned actions, while the app shell's navigationFooter keeps that control outside the scrolling links.

URLs and browser history

Use real href values for destinations, ancestor links and route-based pagination. Keep query, sort, filter and page in the URL when readers need to share or revisit the same view. Respond to browser Back and Forward by reading that state again, not by maintaining a second, disconnected page index.

The kit's provider accepts a link adapter for internal routes. Forward native anchor props, refs and event handlers to preserve accessible names, modified clicks and browser behavior. A command-menu option is an action selection; main-navigation links remain the route for copying a URL or opening a destination in another tab.

Current state and focus

PatternMeaningful stateKeyboard behavior
Destination or page linkaria-current="page"Tab and Enter; browser link modifiers
Tabaria-selected, associated tabpanelArrow/Home/End movement; automatic or manual activation
Workflow stagearia-current="step", explicit status and disabled stateTab, Enter and Space
Command resultActive descendant in a named listboxInput editing, arrows and explicit Enter selection

Selection and keyboard focus remain distinct. A route transition should announce or focus its new heading when appropriate. A tab should follow its activation model. Paging should preserve the controls during loading and announce accepted results. A command dialog restores focus on dismissal. None of these components determines whether a server operation succeeded.

Responsive composition

Use the shell's navigation column on a wide screen and its small primary destination set on a phone. Keep the current destination and essential names available when switching between a panel and rail. Breadcrumbs disclose middle ancestors, pagination offers a compact summary, and steps can become vertical. Large type and translated labels must fit without shrinking targets or relying on a tooltip for essential content.

Navigation uses interface body, label and caption roles. Current states use the shared selected surface and action foreground; focus uses its independent ring. Report-reading typography stays in the document body. Directional icons and decorative artwork accompany names rather than replacing them.

TokenValueremPreview
--bk-navigation-group-gapvar(--space-xl)——
--bk-navigation-item-gapvar(--space-xs)——
--bk-navigation-item-insetvar(--space-md)——
--bk-navigation-avatar-size2em——
--bk-navigation-menu-width16em——
--bk-navigation-column-width18em——
--bk-navigation-submenu-width18em——
--bk-navigation-content-insetvar(--space-xl)——
--bk-navigation-icon-size1.25em——
--bk-navigation-line-width1px0.0625rem

Loading, unavailable destinations and errors

Keep an unavailable destination visible when its presence explains the task, with a reason and the appropriate disabled behavior. Do not turn an unavailable command into a link that appears to work. Keep request failures near the affected collection, and provide retry without erasing a query or draft.

Progress is explicit: reaching a later workflow screen does not mark earlier work complete. A missing total is not an empty collection. A highlighted command is not an executed action. The caller owns each of these decisions.

Do

Hierarchy above the heading; local views above content; page controls beside collection results.

Give each pattern one clear responsibility.
Don't

One tab row mixes pages, filters, workflow stages and destructive actions.

Do not make visually similar controls change unrelated kinds of state.