Navigation Pattern Unification

Visual Arts & Design Intermediate web-polish-skills universal
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">
            &copy; {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.