Active Navigation State in the Sidebar — v1.0.19
Active Navigation State in the Sidebar
Introduced in v1.0.19
Overview
The dashboard sidebar now clearly highlights the link that corresponds to the page you are currently viewing. This makes it easy to orient yourself within the application at a glance, without needing to read the page heading or URL bar.
What Was the Problem?
Prior to v1.0.19, all 10 sidebar links were rendered with identical styling:
text-muted-foreground hover:bg-muted hover:text-foreground
No active state classes were applied. Every link looked the same whether you were on that page or not, making the sidebar effectively useless as a location indicator.
How It Works Now
The sidebar uses the Next.js usePathname() hook (via a dedicated SidebarNav client component) to read the current route on every navigation and apply the appropriate styles.
Active Link Styles
When a link's href matches the current route, it receives:
bg-muted text-foreground font-semibold
This gives the active link a filled background, full-contrast text colour, and bold weight — making it visually distinct from all inactive links.
Matching Logic
| Match Type | Condition | Example |
|---|---|---|
| Exact match | pathname === href | /dashboard/submissions |
| Section match | pathname.startsWith(href) | /dashboard/transactions/123 highlights the Transactions link |
The startsWith check ensures that nested or detail pages (e.g. a specific transaction or submission record) still highlight the correct parent section in the sidebar.
Technical Implementation
Because usePathname() is a React client-side hook, it cannot be used directly inside a Next.js server component. The fix involved:
- Keeping
layout.tsxas a server component — preserving server-side rendering benefits for the overall dashboard shell. - Creating a
SidebarNavclient component — a dedicated'use client'component responsible solely for rendering the list of nav links with active state awareness. - Composing them together —
layout.tsxrenders<SidebarNav />inside the sidebar, passing the route definitions as props.
This pattern keeps the client boundary as narrow as possible while enabling dynamic active state detection.
Before & After
Before (all links identical):
// Every link — same classes, no active awareness
<Link
href="/dashboard/submissions"
className="text-muted-foreground hover:bg-muted hover:text-foreground"
>
Submissions
</Link>
After (active link highlighted):
// Inside SidebarNav client component
'use client';
import { usePathname } from 'next/navigation';
const pathname = usePathname();
<Link
href={href}
className={
pathname === href || pathname.startsWith(href)
? 'bg-muted text-foreground font-semibold'
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
}
>
{label}
</Link>