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?