People need to navigate between dashboards while keeping space for the data.
Updated September 10, 2026
Problem
People need to move among dashboards and know where they are without giving most of the screen to navigation.
Solution
Place navigation in a side rail and keep the main area for dashboard content. Clearly mark the current location.
Keep the rail focused on useful destinations. Use search or grouping when the list becomes too long, and preserve readable names rather than relying on unexplained icons.
Allow collapse when more content space is needed. On narrow screens, provide a clear way to reopen navigation and keep the current page identity visible.
Use when
The product has many destinations and people switch between them regularly.
Don’t use when
There are four destinations. A rail holding four links is a horizontal nav rotated ninety degrees and costing more width. Also avoid it on anything going on a wall, where nobody is navigating and the rail is pure loss.
Trade-offs
A permanent rail reduces space for data. Deep trees truncate labels and create competing scroll areas. A growing menu needs maintenance or it becomes another place people struggle to navigate.
Checklist
What is in the rail: global sections, current-section contents, or both?
Does it show current position clearly enough to orient someone who arrived from a link?
What happens at two hundred items, and is there search?
Do labels truncate, and are truncated labels still distinguishable?
Does the rail scroll independently, and does that fight the page scroll?
Can it collapse, and does anything break when it does?
What replaces it below the mobile breakpoint?
How much horizontal space does it take from the canvas, and is that trade stated?
Does it survive being on a wallboard, or should it be hidden there?
Who decides what gets an entry, and what stops the list growing?
Compare
Grafana runs a collapsible left nav for products and sections with dashboard search as the real navigation, an admission that at real scale a tree stops working and a search box is the honest answer. Netdata puts a node and section tree in the rail and uses it as the page’s table of contents as well as its navigation, so the rail scrolls the canvas rather than replacing it. Kibana splits the difference with a collapsible rail for apps and in-page controls for everything within an app, which keeps the rail short at the cost of two different navigation models to learn. Sentry keeps a thin fixed rail for top-level areas and pushes all narrowing into the content, which suits a product where the work is a queue rather than a library.
Related
Overview then detail provides a hierarchy to navigate. Multi-page dashboard groups peer pages. Tabs as genres fits a smaller set of destinations.
Two-pane list and detail applies a similar split to records. Mobile adaptation changes the navigation for narrow screens.
Wireframe — the pattern's anatomy, not any one product's version of it
Implementation
Persistent navigation across many dashboards without stealing the canvas. A sidebar earns its 240px by showing where you are in a set of fifty, and stops earning it the moment the set is six.
Shell.tsxA rail that scrolls on its own, highlights the current item, collapses on request, and becomes a menu below the breakpoint so the narrow layout keeps its navigation.
import { useState, type ReactNode } from "react";import { Menu, PanelLeftClose } from "lucide-react";import { Button } from "@/components/ui/button";import { ScrollArea } from "@/components/ui/scroll-area";import { cn } from "@/lib/utils";export type NavItem = { id: string; label: string };export type NavSection = { label: string; items: NavItem[] };/** * A rail and a canvas. The rail is narrow, always present, and scrolls on * its own rather than with the page. It highlights the current item, which * is the whole reason it earns its width: "where am I" answered continuously, * for someone who arrived from an alert link and has no idea what else exists. * * Two things are decided here rather than discovered later. Collapse reclaims * the width on a dense page. Below the breakpoint the rail hides, and the menu * button that replaces it opens the same list, so the narrow layout still has * a route everywhere. */export function Shell({ workspace, sections, current, onNavigate, children, railWidth = 240 }: { workspace: string; sections: NavSection[]; /** The id of the item the canvas is showing. Required: a rail without a * current position is a list of links, and a breadcrumb does that. */ current: string; onNavigate: (id: string) => void; children: ReactNode; railWidth?: number;}) { const [collapsed, setCollapsed] = useState(false); const [open, setOpen] = useState(false); // the narrow layout's overlay const rail = ( <nav aria-label="Dashboards" className="flex h-full flex-col"> <div className="flex items-center justify-between px-3 py-2"> <span className="truncate text-sm font-medium">{workspace}</span> <Button variant="ghost" size="sm" className="h-7 w-7 p-0 md:inline-flex hidden" aria-label="Collapse the rail" onClick={() => setCollapsed(true)}> <PanelLeftClose /> </Button> </div> <ScrollArea className="flex-1"> {sections.map((s) => ( <div key={s.label} className="px-1.5 pb-2"> <p className="px-1.5 pt-2 pb-1 text-[11px] uppercase tracking-wide text-muted-foreground">{s.label}</p> {s.items.map((it) => ( <button key={it.id} type="button" aria-current={it.id === current ? "page" : undefined} onClick={() => { onNavigate(it.id); setOpen(false); }} title={it.label} className={cn( "block w-full truncate rounded px-1.5 py-1 text-left text-xs", it.id === current ? "bg-chart-1/20 text-foreground" : "text-muted-foreground hover:bg-muted", )} > {it.label} </button> ))} </div> ))} </ScrollArea> </nav> ); return ( <div className="flex h-[260px] overflow-hidden rounded-lg border bg-card"> {/* Wide: the rail is a column. It never scrolls with the canvas. */} {!collapsed && ( <aside className="hidden shrink-0 border-r md:block" style={{ width: railWidth }}>{rail}</aside> )} <div className="relative flex min-w-0 flex-1 flex-col"> <div className="flex items-center gap-1 border-b px-2 py-1"> <Button variant="ghost" size="sm" className="h-7 w-7 p-0 md:hidden" aria-label="Open navigation" onClick={() => setOpen(true)}> <Menu /> </Button> {collapsed && ( <Button variant="ghost" size="sm" className="hidden h-7 w-7 p-0 md:inline-flex" aria-label="Expand the rail" onClick={() => setCollapsed(false)}> <Menu /> </Button> )} <span className="truncate text-xs text-muted-foreground"> {sections.flatMap((s) => s.items).find((it) => it.id === current)?.label} </span> </div> <ScrollArea className="flex-1 p-3">{children}</ScrollArea> {/* Narrow: the same list, as an overlay the menu button opens. */} {open && ( <aside className="absolute inset-y-0 left-0 z-10 w-[240px] border-r bg-card shadow-md md:hidden"> <Button variant="ghost" size="sm" className="absolute right-1 top-1 h-7 w-7 p-0" aria-label="Close navigation" onClick={() => setOpen(false)}> <PanelLeftClose /> </Button> {rail} </aside> )} </div> </div> );}
demo.tsxHow it is called: two sections, checkout as the current page, three panels on the canvas.
Screenshots are grouped by product and include the capture date.
Dashed boxes highlight this pattern. Notes appear below each screenshot and when you hover over a box.
Demo dashboard at phone width September 10, 2026Home Assistant public demo (signed out, 390px viewport)medium · light · mobile
The desktop dashboard reflows into a narrow phone layout while keeping its room sections and controls. Cards remain in two columns within sections, limiting the page’s length. The section order differs from the desktop columns, so the responsive layout changes what people encounter first. Navigation moves behind a menu button, while the summary chips remain at the top.
Mobile adaptationThe phone layout changes the reading order of the desktop sections.
Demo dashboard September 10, 2026Home Assistant public demo (signed out)medium · light · desktop-web
Cards are grouped by rooms such as Living room, Kitchen, and Study. States are written in words, with icon color providing a second cue. Section headers add useful room summaries, such as temperature or meeting status. The page also contains controls, including lighting sliders and thermostat buttons, so people can act in the same place they check the state.
Metrics / System September 10, 2026Netdata Agent v2.10.0-686-nightly (public registry node, signed out)dense · dark · desktop-web
The navigation reports 720 charts organized by the metrics collected from the system. The main page follows the same nested structure, giving broad coverage but little indication of which charts matter most to a particular task. Search helps navigate that volume. The header also distinguishes live and stale nodes, making missing updates visible instead of presenting every node as current.
Sidebar and canvasThe rail organizes collected metrics into a hierarchy.