Navigation Pattern Unification
0
Upvotes
3
Views
1
Downloads
1,045
Words
Description
Fix inconsistent site navigation by unifying headers, footers, sidebars, and breadcrumbs across pages.
When to Use
Help unify my site's navigation patterns | I need a consistent header, footer, and breadcrumbs | Audit navigation across pages for consistency | Standardize mobile and desktop navigation | Create a unified navigation system
Use Cases
Audit site nav for consistency across pages | Create a single header component with variants | Standardize footer and breadcrumb styles | Migrate mobile nav to a responsive hamburger menu
SKILL.md Content
---
name: navigation-patterns
description: "Fix inconsistent site navigation by unifying headers, footers, sidebars, and breadcrumbs across pages."
metadata:
tags: "visual-arts-design, navigation, ui-patterns, header-unification, footer-unification, breadcrumbs, navigation-audit"
source: "https://skilldb.dev/skills/web-polish-skills/navigation-patterns"
pack: "web-polish-skills"
category: "Visual Arts & Design"
---
# Navigation Pattern Unification
## When to use this skill
Use when the user says things like:
- "Help unify my site's navigation patterns"
- "I need a consistent header, footer, and breadcrumbs"
- "Audit navigation across pages for consistency"
- "Standardize mobile and desktop navigation"
- "Create a unified navigation system"
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
1. **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.
2. **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.
3. **Mobile menu is an afterthought.** Desktop nav wraps and overlaps on mobile instead of
collapsing to a hamburger menu.
4. **No breadcrumbs.** Users get lost in nested pages with no way to navigate back.
5. **Footer doesn't match header.** Different fonts, different link styles, different layout grid.
6. **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:
```tsx
// 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
```tsx
// 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)
```tsx
// 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
```tsx
// 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
```tsx
// 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
```tsx
// 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.