Modern dashboards demand navigation that’s dynamic, reactive, and context-aware.
In React Router apps, useLocation
is your go-to hook for reading the current URL and making UI decisions — like highlighting active menu items in a sidebar.
In this article, we’ll build a collapsible Admin Sidebar with:
✅ useLocation
to detect and highlight active routes
✅ Lucide React icons for a modern look
✅ Collapsible state toggling (ChevronLeft
/ ChevronRight
)
✅ Responsive + accessible design patterns
Why useLocation
?
React Router’s useLocation
hook provides an object with the current URL, including pathname
, search
, and hash
.
In dashboards, this is typically used for:
- Active states: Highlighting the current menu item.
- Contextual conditions: Checking if the route matches a sub-path (
/admin/products/123
). - Debugging / Analytics: Logging path changes or sending analytics events.
Sidebar Implementation
import { Link, useLocation } from 'react-router-dom'; import { Home, Users, BarChart3, Settings, FileText, ShoppingCart, Bell, HelpCircle, ChevronLeft, ChevronRight, } from 'lucide-react'; import { CustomLogo } from '@/components/custom/CustomLogo'; interface SidebarProps { isCollapsed: boolean; onToggle: () => void; } export const AdminSidebar: React.FC<SidebarProps> = ({ isCollapsed, onToggle, }) => { const { pathname } = useLocation(); const menuItems = [ { icon: Home, label: 'Dashboard', to: '/admin' }, { icon: BarChart3, label: 'Products', to: '/admin/products' }, { icon: Users, label: 'Users' }, { icon: ShoppingCart, label: 'Orders' }, { icon: FileText, label: 'Reports' }, { icon: Bell, label: 'Notifications' }, { icon: Settings, label: 'Settings' }, { icon: HelpCircle, label: 'Aid' }, ]; const isActiveRoute = (to: string) => { if (pathname.includes('/admin/products/') && to === '/admin/products') { return true; } return pathname === to; }; return ( <div className={`bg-white border-r border-gray-200 transition-all duration-300 ease-in-out ${ isCollapsed ? 'w-18' : 'w-64' } flex flex-col`} > {/* Header */} <div className="p-4 border-b border-gray-200 flex items-center justify-between h-18"> {!isCollapsed && <CustomLogo />} <button onClick={onToggle} className="p-2 rounded-lg hover:bg-gray-100 transition-colors" > {isCollapsed ? <ChevronRight size={20} /> : <ChevronLeft size={20} />} </button> </div> {/* Navigation */} <nav className="flex-1 p-4"> <ul className="space-y-2"> {menuItems.map((item, index) => { const Icon = item.icon; return ( <li key={index}> <Link to={item.to || '/admin'} className={`flex items-center space-x-3 px-3 py-2 rounded-lg transition-all duration-200 group ${ isActiveRoute(item.to || '/xxxx') ? 'bg-blue-50 text-blue-600 border-r-2 border-blue-600' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900' }`} > <Icon size={20} className="flex-shrink-0" /> {!isCollapsed && ( <span className="font-medium">{item.label}</span> )} </Link> </li> ); })} </ul> </nav> {/* User Profile */} {!isCollapsed && ( <div className="p-4 border-t border-gray-200"> <div className="flex items-center space-x-3 p-3 rounded-lg hover:bg-gray-50 transition-colors cursor-pointer"> <div className="w-10 h-10 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-white font-semibold"> JD </div> <div className="flex-1 min-w-0"> <p className="text-sm font-medium text-gray-900 truncate"> John Doe </p> <p className="text-xs text-gray-500 truncate">john@company.com</p> </div> </div> </div> )} </div> ); };
Key Concepts in This Example
Feature | Purpose |
---|---|
useLocation | Reads current pathname to decide active state |
isActiveRoute() | Custom logic for exact vs. nested matches |
Lucide Icons | Lightweight, consistent icon system (lucide-react ) |
Collapsible sidebar | UX pattern for saving screen real estate |
TailwindCSS classes | Handles spacing, borders, hover states, and transitions |
Advanced Patterns
- Persistent collapse state: Store
isCollapsed
inlocalStorage
or context to remember user preferences. - Role-based menus: Filter
menuItems
based on user roles. - Breadcrumbs: Derive breadcrumbs from
pathname.split('/')
. - Analytics: Fire events whenever
pathname
changes with auseEffect
onlocation
.
Summary
With just a few hooks and thoughtful design, you can build:
- A responsive, collapsible admin sidebar
- Context-aware active state highlighting
- A scalable pattern for menu item configuration
This approach makes your dashboard navigation clean, declarative, and future-proof. ⚡
Top comments (1)
Absolutely love how you broke this down!
I’ve built a few admin dashboards myself, and using useLocation for active route highlighting is such a clean approach، way better than juggling state manually. Also, making the sidebar collapsible while keeping it responsive and context-aware really improves UX, especially on complex dashboards.
One extra tip from my experience: persisting the collapse state in localStorage or context makes it feel even more polished for returning users. Curious، have you experimented with role-based menu filtering for multi-role dashboards?