Navigation Patterns
Fix inconsistent navigation patterns — mismatched headers, footers, sidebars, breadcrumbs, tab bars, and mobile menus. Establish unified navigation components that work across all pages. Trigger when nav "looks different on different pages," active states don't match, or mobile menu is broken/missing.
Navigation is the skeleton of a website. When the header changes height between pages, active states use different indicators on different pages, or the mobile menu is a different implementation from the desktop nav, every page feels like a different website. A single Header component, a single Footer component, and a single Sidebar component -- each used on every page -- is the minimum requirement for navigation consistency. ## Key Points 1. **Header changes between pages.** The home page has a transparent header with white text, 2. **Active state varies.** On one page the active nav item has an underline, on another it 3. **Mobile menu is an afterthought.** Desktop nav wraps and overlaps on mobile instead of 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 - 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.
skilldb get web-polish-skills/navigation-patternsFull skill: 318 linesNavigation Pattern Unification
Core Philosophy
Navigation is the skeleton of a website. When the header changes height between pages, active states use different indicators on different pages, or the mobile menu is a different implementation from the desktop nav, every page feels like a different website. A single Header component, a single Footer component, and a single Sidebar component -- each used on every page -- is the minimum requirement for navigation consistency.
Active states must be identical everywhere. If the home page shows an underline for the active nav item and the about page shows a background color, users lose the visual cue that tells them where they are. One active state indicator, applied consistently through one NavLink component, is the fix. This is a small change with outsized impact on perceived quality.
Mobile navigation is not optional or secondary. If the desktop navigation wraps and overlaps on a 375px viewport instead of collapsing to a hamburger menu, the site is broken on the device that most users actually use. Every desktop navigation pattern must have a mobile equivalent that provides the same functionality with touch-appropriate targets and a slide-out or bottom-sheet interaction model.
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.
Install this skill directly: skilldb add web-polish-skills
Related Skills
Responsive Repair
Fix broken responsive layouts in a vibecoded website — content overflowing on mobile, misaligned grids, missing breakpoint handling, touch targets that are too small, and navigation that doesn't adapt. Trigger when things "break on mobile," "look bad on tablet," or "don't resize properly."
Typography Cleanup
Fix font chaos in a vibecoded website — too many font sizes, inconsistent weights, broken heading hierarchy, mismatched line heights, and mixed font families. Establish a clean type scale and apply it everywhere. Trigger when fonts "look off," headings are inconsistent, or there are too many text sizes.
UI Audit
Audit a vibecoded or inconsistent website for visual and structural problems — mismatched components, font chaos, color drift, spacing irregularities, broken states, and missing interactions. Produces a prioritized fix list. Trigger when users say "this looks off," "make it consistent," "audit the UI," "fix the design," or "polish this site."
Vibecode Upgrade
The comprehensive playbook for upgrading a vibecoded website from "it works" to "it feels premium." Combines all other web-polish skills into a prioritized workflow — audit, extract tokens, unify components, fix responsiveness, add polish. Trigger when users want to "upgrade the whole site," "make it look professional," "polish everything," or "it was vibecoded and needs to be cleaned up."
Visual Polish
Apply the final layer of visual polish to a website — hover states, transitions, micro- interactions, loading states, empty states, scroll behavior, and the small details that make a site feel premium. Trigger when a site "works but feels cheap," needs "polish," or the user says "make it feel premium/professional/finished."
Web Design Fundamentals
Use this skill when designing a web page or interface from scratch rather than repairing one. Activate when users mention web design, web-design, visual design, layout design, typography choices, color palette selection, spacing systems, visual hierarchy, landing page design, or "make this look professional / less like a bootstrap template".