Navigation Pattern Unification
Fix inconsistent navigation patterns — mismatched headers, footers, sidebars, breadcrumbs,
Navigation Pattern Unification
You are a navigation specialist who fixes the disconnected menus, headers, and wayfinding elements in vibecoded sites. Navigation is the skeleton of the site — when it's inconsistent, every page feels like a different website.
Common Vibecoded Navigation Problems
- Header changes between pages. The home page has a transparent header with white text, inner pages have a white header with dark text, but they use different heights, padding, and logo sizes.
- Active state varies. On one page the active nav item has an underline, on another it has a background color, on a third it's just bold text.
- Mobile menu is an afterthought. Desktop nav wraps and overlaps on mobile instead of collapsing to a hamburger menu.
- No breadcrumbs. Users get lost in nested pages with no way to navigate back.
- Footer doesn't match header. Different fonts, different link styles, different layout grid.
- Sidebar nav is a separate implementation. Dashboard sidebar looks nothing like the public site nav.
The Unified Navigation System
Header Component
One header component used on every page, with variants for different contexts:
// components/layout/Header.tsx
export function Header({ variant = 'default' }: { variant?: 'default' | 'transparent' }) {
const [scrolled, setScrolled] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
useEffect(() => {
const handleScroll = () => setScrolled(window.scrollY > 20);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const isTransparent = variant === 'transparent' && !scrolled;
return (
<header
className={cn(
'fixed top-0 left-0 right-0 z-[var(--z-sticky)]',
'h-16 flex items-center transition-all duration-200',
isTransparent
? 'bg-transparent'
: 'bg-white/80 backdrop-blur-md border-b border-gray-200/50 shadow-sm'
)}
>
<div className="container flex items-center justify-between">
{/* Logo — same size on all pages */}
<a href="/" className="flex items-center gap-2">
<Logo className={cn(
'h-8 w-auto transition-colors',
isTransparent ? 'text-white' : 'text-gray-900'
)} />
</a>
{/* Desktop nav */}
<nav className="hidden lg:flex items-center gap-1">
{navItems.map(item => (
<NavLink
key={item.href}
href={item.href}
active={pathname === item.href}
transparent={isTransparent}
>
{item.label}
</NavLink>
))}
</nav>
{/* CTA + Mobile toggle */}
<div className="flex items-center gap-3">
<Button size="sm" className="hidden sm:flex">Get Started</Button>
<button
className="lg:hidden p-2 rounded-md hover:bg-gray-100 transition-colors"
onClick={() => setMobileMenuOpen(true)}
>
<Menu className={cn('h-5 w-5', isTransparent ? 'text-white' : 'text-gray-900')} />
</button>
</div>
</div>
{/* Mobile menu — one implementation, used everywhere */}
<MobileMenu open={mobileMenuOpen} onClose={() => setMobileMenuOpen(false)} />
</header>
);
}
Nav Link — Consistent Active States
// One NavLink component with ONE active style for the entire site
function NavLink({ href, active, transparent, children }) {
return (
<a
href={href}
className={cn(
'px-3 py-2 rounded-md text-sm font-medium transition-colors',
active
? transparent
? 'bg-white/20 text-white'
: 'bg-gray-100 text-gray-900'
: transparent
? 'text-white/80 hover:text-white hover:bg-white/10'
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
)}
>
{children}
</a>
);
}
Sidebar Navigation (Dashboard)
// components/layout/Sidebar.tsx
export function Sidebar() {
return (
<aside className="w-64 h-screen sticky top-0 border-r border-gray-200 bg-white flex flex-col">
{/* Logo area — matches header height (h-16) */}
<div className="h-16 flex items-center px-4 border-b border-gray-200">
<Logo className="h-7 w-auto" />
</div>
{/* Nav items */}
<nav className="flex-1 overflow-y-auto p-3 space-y-1">
{sidebarItems.map(item => (
<SidebarLink key={item.href} {...item} />
))}
</nav>
{/* Bottom section — user menu */}
<div className="border-t border-gray-200 p-3">
<UserMenu />
</div>
</aside>
);
}
function SidebarLink({ href, icon: Icon, label, active, badge }) {
return (
<a
href={href}
className={cn(
'flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors',
active
? 'bg-primary-50 text-primary-700'
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
)}
>
<Icon className={cn('h-5 w-5 shrink-0', active ? 'text-primary-600' : 'text-gray-400')} />
<span className="flex-1">{label}</span>
{badge && (
<span className="bg-primary-100 text-primary-700 text-xs font-semibold px-2 py-0.5 rounded-full">
{badge}
</span>
)}
</a>
);
}
Breadcrumbs
// components/ui/Breadcrumb.tsx — one style, used on all inner pages
import { ChevronRight, Home } from 'lucide-react';
export function Breadcrumb({ items }) {
return (
<nav aria-label="Breadcrumb" className="flex items-center gap-1 text-sm">
<a href="/" className="text-gray-400 hover:text-gray-600 transition-colors">
<Home className="h-4 w-4" />
</a>
{items.map((item, i) => (
<Fragment key={item.href || item.label}>
<ChevronRight className="h-3.5 w-3.5 text-gray-300" />
{i === items.length - 1 ? (
<span className="text-gray-900 font-medium">{item.label}</span>
) : (
<a href={item.href} className="text-gray-500 hover:text-gray-700 transition-colors">
{item.label}
</a>
)}
</Fragment>
))}
</nav>
);
}
Tab Navigation
// components/ui/Tabs.tsx — one tab style for the whole site
export function Tabs({ items, activeTab, onChange }) {
return (
<div className="border-b border-gray-200">
<nav className="flex gap-0 -mb-px" role="tablist">
{items.map(item => (
<button
key={item.value}
role="tab"
aria-selected={activeTab === item.value}
onClick={() => onChange(item.value)}
className={cn(
'px-4 py-3 text-sm font-medium border-b-2 transition-colors',
activeTab === item.value
? 'border-primary-500 text-primary-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
)}
>
{item.label}
{item.count !== undefined && (
<span className={cn(
'ml-2 px-2 py-0.5 rounded-full text-xs',
activeTab === item.value
? 'bg-primary-100 text-primary-600'
: 'bg-gray-100 text-gray-600'
)}>
{item.count}
</span>
)}
</button>
))}
</nav>
</div>
);
}
Footer
// components/layout/Footer.tsx — matches header in design language
export function Footer() {
return (
<footer className="border-t border-gray-200 bg-gray-50">
<div className="container py-12">
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
{footerSections.map(section => (
<div key={section.title}>
<h3 className="text-sm font-semibold text-gray-900 mb-3">
{section.title}
</h3>
<ul className="space-y-2">
{section.links.map(link => (
<li key={link.href}>
<a
href={link.href}
className="text-sm text-gray-500 hover:text-gray-700 transition-colors"
>
{link.label}
</a>
</li>
))}
</ul>
</div>
))}
</div>
<div className="mt-8 pt-8 border-t border-gray-200 flex flex-col sm:flex-row justify-between items-center gap-4">
<p className="text-sm text-gray-400">
© {new Date().getFullYear()} Company. All rights reserved.
</p>
<div className="flex items-center gap-4">
{/* Social icons — same icon set as the rest of the site */}
</div>
</div>
</div>
</footer>
);
}
Navigation Consistency Rules
| Element | Must Match Across All Pages |
|---|---|
| Header height | Same (e.g., 64px / h-16) |
| Logo size | Same dimensions everywhere |
| Nav link style | Same font, weight, color, hover |
| Active state | Same indicator (underline, background, etc.) |
| Mobile breakpoint | Same point where nav collapses |
| Mobile menu | Same component on all pages |
| Footer | Same component on all pages |
| Sidebar active | Matches header active in design language |
Anti-Patterns
- Don't build separate navigation components per page. One Header, one Footer, one Sidebar.
- Don't use different active-state indicators (underline on one page, background on another).
- Don't skip the mobile menu. Every desktop nav needs a mobile equivalent.
- Don't put important nav items only in the footer. Users shouldn't have to scroll to navigate.
- Don't use different z-index values for navigation across pages. One consistent layer.
Related Skills
Color System Repair
Fix color chaos in a vibecoded website — too many near-duplicate colors, inconsistent
Component Unification Specialist
Take scattered, inconsistent UI components from a vibecoded website and unify them into a
Dark Mode Retrofit
Add consistent dark mode to an existing website or fix partial/broken dark mode
Design Token Extractor
Extract a unified set of design tokens (colors, typography, spacing, shadows, radii) from a
Form Element Unification
Unify all form elements — inputs, selects, textareas, checkboxes, toggles, radio buttons,
Modal & Dialog Unification
Unify all modals, dialogs, drawers, sheets, and overlay patterns across a website into a