vwf .

Developer Docs

Quick reference guides with props tables, usage examples, and best practices for rapid development

Components/Docs/NavigationHeader

NavigationHeader

Primary app navigation header with logo, mobile/desktop navigation, and hamburger/back button

NavigationHeader

Comprehensive responsive navigation header with desktop horizontal navigation, mobile bottom navigation bar, hamburger/back button, notification bell, user dropdown menu, and integrated navigation drawer.

💡 See It In Action

NavigationHeader is currently active at the top of this page! Try clicking the hamburger menu (☰) to open the navigation drawer, or view the navigation links in the header.

Live Example

Interactive NavigationHeader demo (scaled down to fit):

Demo Content Area

This is a scaled-down demo showing NavigationHeader functionality. Try clicking the hamburger menu or navigation links above.

Key Features

✓Desktop horizontal navigation with dropdown menus

✓Mobile fixed bottom navigation bar

✓Context-aware hamburger/back button

✓Notification bell with reactive badge count

✓User avatar with dropdown (Profile, Settings, Logout)

✓Active tab highlighting based on current route

✓HMR state persistence (survives hot reloads)

✓NavigationDrawer integration with shared state

🔧 Props Reference

All available props for the NavigationHeader component:

PropTypeDefaultDescription
navLinksArray[]Primary navigation links (visible on all screens, shown in mobile bottom nav)
dsktpNavLinksArray[]Desktop-only links (visible at lg+ breakpoint in header)
navStructureObjectnullOptional dropdown structure for complex navigation hierarchies
defaultTabstring''Default active tab on mount
classstring''Additional header CSS classes

Nav Link Object Structure

PropTypeDefaultDescription
pathstringrequiredNavigation path (e.g., "/services")
labelstringrequiredDisplay label for the link
iconstringundefinedOracle JET icon name (for mobile nav)
dropdownArrayundefinedOptional dropdown items array

⚠️ Return Value

IMPORTANT: NavigationHeader returns an array [drawer, header] - you must spread it!

// ✅ CORRECT - Spread the array
const app = div(
  ...NavigationHeader({
    navLinks,
    dsktpNavLinks
  }),
  main(/* page content */)
);

// ❌ WRONG - Don't nest in another element
const app = div(
  div(NavigationHeader({ navLinks })),  // WRONG!
  main(/* page content */)
);

💻 Basic Usage

Simple example showing how to integrate NavigationHeader into your app layout:

import { NavigationHeader } from 'shared-components/layout/NavigationHeader.js';

// Define navigation links
const navLinks = [
  { path: '/dashboard', label: 'Dashboard', icon: 'home' },
  { path: '/services', label: 'Services', icon: 'grid' },
  { path: '/messages', label: 'Messages', icon: 'message' },
  { path: '/profile', label: 'Profile', icon: 'person' }
];

// Desktop-only links
const dsktpNavLinks = [
  { path: '/discover', label: 'Discover' },
  { path: '/about', label: 'About' }
];

// Main app layout
export const App = () => {
  return div(
    // Spread NavigationHeader array
    ...NavigationHeader({
      navLinks,
      dsktpNavLinks
    }),

    // Main content
    main(
      { class: 'container mx-auto p-4' },
      // Page content here
    )
  );
};

📋 With Dropdown Menus

Desktop navigation supports dropdown menus for complex hierarchies:

const dsktpNavLinks = [
  {
    path: '/components',
    label: 'Components',
    dropdown: [
      { path: '/components/forms', label: 'Forms', icon: 'edit' },
      { path: '/components/display', label: 'Display', icon: 'layout' },
      { path: '/components/feedback', label: 'Feedback', icon: 'bell' },
      { separator: true },
      { path: '/components/all', label: 'View All', icon: 'grid' }
    ]
  },
  {
    path: '/framework',
    label: 'Framework',
    dropdown: [
      { path: '/framework/routing', label: 'Routing' },
      { path: '/framework/state', label: 'State Management' },
      { path: '/framework/styling', label: 'Styling' }
    ]
  }
];

NavigationHeader({
  navLinks,
  dsktpNavLinks
})

📱 Responsive Behavior

NavigationHeader adapts its layout and functionality based on screen size:

Desktop (lg+ breakpoint)

✓Horizontal navigation in header

✓navLinks + dsktpNavLinks both visible

✓Dropdowns appear below nav items

✓Logo in top-left

✓Notification bell + user avatar in top-right

Mobile (<lg breakpoint)

✓Fixed bottom navigation bar

✓Only navLinks visible (with icons)

✓Hamburger button opens drawer

✓Back button on subpages

✓Header shows logo + notifications + user avatar

🔄 Hamburger/Back Button Logic

The left button is context-aware and changes based on current state:

1. On Main Pages (/, /services, /dashboard, etc.)

→ Shows hamburger icon

→ Click opens NavigationDrawer

2. On Subpages (/services/123, /profile/edit, etc.)

→ Shows back arrow

→ Click navigates back in history

3. When Drawer is Open

→ Shows X icon

→ Click closes NavigationDrawer

🎯 Active State Tracking

NavigationHeader automatically highlights the current active tab based on URL:

// Active state is determined by URL path matching
// If current URL is /services/123
// Then the /services nav link will be highlighted

// Active styles applied:
// - text-midnight (vs text-secondary)
// - border-sky (vs border-transparent)
// - Bottom border shows blue accent

// Works with nested routes:
// /services        → highlights Services
// /services/new    → highlights Services
// /services/123    → highlights Services

👤 User Dropdown Menu

User avatar in top-right opens dropdown with:

✓Profile → /profile

✓Settings → /settings

✓Logout → Calls /api/v1/auth/logout, redirects to /auth/

Logout Flow

// Built-in logout handler:
// 1. Calls POST /api/v1/auth/logout
// 2. Stores previous login info in localStorage
// 3. Clears auth store
// 4. Redirects to /auth/loginas/ (or /auth/ if no previous login)

// Logout is automatic - no custom handler needed!

🔔 Notification Bell

Shows notification badge when hasNotifications is true:

// Currently mock data (replace with real notifications):
const hasNotifications = van.state(true);
const notificationCount = van.state(3);

// Badge shows on bell icon when notifications exist
// Click handler is in NavigationHeader source (lines ~320-330)

🔥 HMR State Persistence

NavigationHeader preserves state across hot module reloads (HMR):

// Global state store survives HMR
window.__navigationHeaderState = {
  showDropdown: van.state(false),
  isDrawerOpen: van.state(false),
  activeDropdown: van.state(null)
};

// This means:
// - Open dropdown → save code → dropdown stays open
// - Open drawer → save code → drawer stays open
// - Improves development experience!

✅ Best Practices

Follow these guidelines for optimal NavigationHeader implementation:

✓ Do This

✓Spread the returned array (...NavigationHeader)

✓Provide icons for mobile navigation

✓Limit navLinks to 4-5 items for mobile

✓Use dropdowns for complex hierarchies

✓Keep labels short (1-2 words)

✓Define navigation in layout.js

✓Test on mobile viewports

✗ Avoid This

✗Nest the returned array in another element

✗Omit mobile icons from navLinks

✗Overcrowd navigation (>5 items)

✗Use long labels that wrap on mobile

✗Hardcode navigation links

✗Modify global state directly

🔗 Integration with Router

NavigationHeader automatically integrates with the file-based router for navigation and active state tracking:

import { router, navigateTo } from 'utils/fileBasedRouterV2.js';
import { navigationPath } from 'utils/state.js';
import { NavigationHeader } from 'shared-components/layout/NavigationHeader.js';

// NavigationHeader automatically:
// 1. Listens to navigationPath for active state
// 2. Uses navigateTo() for navigation
// 3. Handles browser back/forward buttons
// 4. Updates active state on URL change

// Just provide the navigation structure:
const navLinks = [ /* your links */ ];

div(
  ...NavigationHeader({ navLinks }),
  main(/* routed content */)
)

📦 Complete Example

Full implementation example showing NavigationHeader with all features:

import van from 'vanjs-core';
import { NavigationHeader } from 'shared-components/layout/NavigationHeader.js';

const { div, main } = van.tags;

// Define navigation structure
const navLinks = [
  { path: '/', label: 'Home', icon: 'home' },
  { path: '/services', label: 'Services', icon: 'grid' },
  { path: '/messages', label: 'Messages', icon: 'message' },
  { path: '/profile', label: 'Profile', icon: 'person' }
];

const dsktpNavLinks = [
  {
    path: '/discover',
    label: 'Discover',
    dropdown: [
      { path: '/discover/services', label: 'Services' },
      { path: '/discover/neighbors', label: 'Neighbors' },
      { path: '/discover/events', label: 'Events' }
    ]
  },
  { path: '/about', label: 'About' },
  { path: '/help', label: 'Help' }
];

// Main app layout
export const App = () => {
  return div(
    // Navigation (drawer + header)
    ...NavigationHeader({
      navLinks,
      dsktpNavLinks,
      defaultTab: '/'
    }),

    // Main content area
    main(
      { class: 'container mx-auto px-4 py-6 mb-20 lg:mb-0' },
      // ↑ mb-20 for mobile bottom nav clearance

      // Your routed content here
    )
  );
};

🔧 Common Issues & Solutions

Solutions to common NavigationHeader integration problems:

Issue: Content hidden under header

// NavigationHeader is sticky top-0
// Add padding to main content:
main({ class: 'pt-16' }, /* content */)
// Or use mt-16 on first child

Issue: Mobile nav covers bottom content

// Mobile bottom nav is fixed bottom-0
// Add margin-bottom to content:
main({ class: 'mb-20 lg:mb-0' }, /* content */)
// mb-20 on mobile, mb-0 on desktop

Issue: Active state not updating

// NavigationHeader listens to window.location.pathname
// Ensure you're using navigateTo() from the router:
import { navigateTo } from 'utils/fileBasedRouterV2.js';

// Not window.location.href or <a href>
navigateTo('/services');

Explore More Components

Browse other component categories