0% found this document useful (0 votes)
6 views6 pages

Multi Navigation HTML CSS JS Project

Uploaded by

stanjohnes2
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views6 pages

Multi Navigation HTML CSS JS Project

Uploaded by

stanjohnes2
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 6

Multi-level Responsive Navigation (Dynamic)

Multi-level Responsive Navigation (Dynamic)


==========================================

This project demonstrates a responsive, accessible, dynamic multi-level navigation built with HTML, CSS and JavaScript.
It programmatically generates 20 top-level menu items with 6 sublinks each (120 sublinks total), includes responsive
hamburger menu,
collapsible submenus, smooth open/close animations, keyboard accessibility, and a dark-mode toggle stored in
localStorage.

How it works:
- HTML contains a minimal scaffold and placeholders.
- CSS provides layout, responsive behaviour, transitions and accessible focus styles.
- JS generates the menu structure (so you get 120+ links without manually typing them) and manages interactions:
- toggling the mobile menu
- expanding/collapsing submenus
- keyboard navigation (Arrow keys, Enter, Esc)
- smooth scroll-to-anchor for demonstration
- dark-mode persistence.

You can copy the full code below into a single `index.html` file and open it in the browser.

Full Code (copy into index.html):


<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Multi Navigation — Demo</title>
<style>
:root{
--bg: #ffffff; --fg: #111827; --muted:#6b7280; --accent:#06b6d4;
--nav-bg:#f8fafc; --nav-border: rgba(15,23,42,0.06);
--radius:8px;
}
[data-theme="dark"]{
--bg: #0b1220; --fg: #e6eef8; --muted:#9aa6b2; --accent:#7dd3fc;
--nav-bg:#071022; --nav-border: rgba(255,255,255,0.06);
}
*{box-sizing:border-box}
body{font-family:Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; margin:0;b
.container{max-width:1200px;margin:0 auto;padding:1rem;}
.header{display:flex;align-items:center;justify-content:space-between;gap:1rem;background:var(--nav-bg);padding:.5rem 1r
.brand{font-weight:700;display:flex;gap:.5rem;align-items:center}
.controls{display:flex;gap:.5rem;align-items:center}
button.icon{background:transparent;border:0;padding:.4rem;cursor:pointer;font-size:1rem;border-radius:6px}
button.icon:focus{outline:3px solid rgba(6,182,212,.18)}

/* NAV */
.nav-wrap{display:flex;align-items:center;gap:1rem}
nav.primary{display:flex;gap:0.5rem;align-items:stretch}
nav.primary ul{list-style:none;margin:0;padding:0;display:flex;gap:.25rem}
nav.primary li{position:relative}
nav.primary > ul > li > a{display:inline-flex;align-items:center;gap:.5rem;padding:.5rem .75rem;border-radius:6px;text-d
nav.primary a:focus{outline:3px solid rgba(6,182,212,.12)}

/* dropdowns */
.submenu{position:absolute;left:0;top:calc(100% + 6px);min-width:240px;background:var(--bg);border:1px solid var(--nav-b
li.open > .submenu, li:hover > .submenu{opacity:1;transform:translateY(0) scale(1);pointer-events:auto}

/* nested submenu inside submenu (flyout) */


.submenu ul{display:block}
.submenu .has-children{position:relative}
.submenu .has-children > .submenu{left:100%;top:0;margin-left:6px}

/* mobile */
.mobile-toggle{display:none}
@media (max-width: 900px){
nav.primary{display:none;position:relative}
.mobile-toggle{display:block}
.mobile-nav{display:block;width:100%;}
.mobile-panel{position:fixed;inset:0;background:rgba(2,6,23,.5);backdrop-filter:blur(4px);display:none;padding:1rem;z-
.mobile-panel.open{display:block}
.mobile-menu{background:var(--bg);max-width:420px;height:100%;overflow:auto;padding:1rem;border-radius:8px;box-shadow:
.mobile-menu ul{display:block}
.mobile-menu a{display:flex;justify-content:space-between;padding:.6rem .75rem;border-radius:6px;text-decoration:none;
.mobile-menu .submenu{position:static;opacity:1;transform:none;border:0;padding-left:.5rem;display:none}
.mobile-menu .open > .submenu{display:block}
.mobile-menu .chev{transform:rotate(0);transition:transform .15s ease}
.mobile-menu .open > a .chev{transform:rotate(90deg)}
}

/* buttons and utility */


.kv{font-size:.9rem;color:var(--muted)}
.small{font-size:.85rem}
.theme-toggle{padding:.4rem .6rem;border-radius:6px;background:linear-gradient(180deg,rgba(0,0,0,.02),transparent);borde

/* accessibility focus */
a:focus, button:focus{outline-offset:2px}

/* content demo anchors */


section.demo{padding:3rem 1rem;border-bottom:1px dashed var(--nav-border);min-height:36vh}
.demo h2{margin-top:0}
</style>
</head>
<body>
<header class="header">
<div class="brand container">
<div style="display:flex;align-items:center;gap:.5rem">
<strong>MultiNav</strong><span class="kv small">Demo</span>
</div>
<div class="controls">
<button class="icon mobile-toggle" id="mobileBtn" aria-label="Open menu" aria-expanded="false">■</button>
<button class="theme-toggle" id="themeToggle" aria-pressed="false">Toggle Theme</button>
</div>
</div>
</header>

<div class="nav-wrap container" aria-label="Main navigation area">


<nav class="primary" id="primaryNav" aria-label="Primary navigation" role="navigation">
<!-- menu will be generated here -->
</nav>
</div>

<!-- mobile panel -->


<div class="mobile-panel" id="mobilePanel" aria-hidden="true">
<div class="mobile-menu" role="dialog" aria-modal="true" aria-label="Mobile menu" id="mobileMenu">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:.5rem">
<strong>Menu</strong>
<button class="icon" id="mobileClose" aria-label="Close menu">✕</button>
</div>
<nav class="mobile-nav" id="mobileNav" aria-label="Mobile navigation">
<!-- mobile menu generated here -->
</nav>
</div>
</div>

<main class="container">
<section class="demo" id="section-home"><h2>Home</h2><p>Demo content area — click links to test smooth scroll.</p></se
<section class="demo" id="section-1"><h2>Section 1</h2><p>Section 1 content.</p></section>
<section class="demo" id="section-2"><h2>Section 2</h2><p>Section 2 content.</p></section>
<section class="demo" id="section-3"><h2>Section 3</h2><p>Section 3 content.</p></section>
</main>

<script>
/* Configuration: generate 20 top-level items, each with 6 sublinks = 120 links */
const TOP = 20;
const SUB = 6;

/* Utility to create link element */


function makeLink(href, text){ const a = document.createElement('a'); a.href = href; a.textContent = text; a.setAttribut

/* Build menu data */


const menuData = Array.from({length: TOP}, (_,i) => {
const id = i+1;
return {
title: `Category ${id}`,
href: `#section-${(id%3)}`,
children: Array.from({length: SUB}, (_,j) => ({
title: `Item ${id}.${j+1}`,
href: `#section-${(j%3)}`,
children: j % 3 === 0 ? [ // some deeper nesting for variety
{ title: `Deep ${id}.${j+1}.1`, href: `#section-${(j%3)}` },
{ title: `Deep ${id}.${j+1}.2`, href: `#section-${(j%3)}` }
]: []
}))
}
});

/* Create desktop nav markup */


function buildDesktopNav(data){
const nav = document.getElementById('primaryNav');
const ul = document.createElement('ul');
ul.setAttribute('role','menubar');
data.forEach(group => {
const li = document.createElement('li');
li.setAttribute('role','none');
const a = makeLink(group.href, group.title);
a.setAttribute('role','menuitem');
a.setAttribute('aria-haspopup', group.children.length ? 'true' : 'false');
a.setAttribute('tabindex', '0');
li.appendChild(a);
if(group.children && group.children.length){
const sub = document.createElement('div');
sub.className = 'submenu';
const subul = document.createElement('ul');
subul.setAttribute('role','menu');
group.children.forEach(child => {
const subli = document.createElement('li');
subli.setAttribute('role','none');
const ca = makeLink(child.href, child.title);
ca.setAttribute('tabindex', '-1');
subli.appendChild(ca);

if(child.children && child.children.length){


subli.classList.add('has-children');
const fly = document.createElement('div'); fly.className = 'submenu';
const flyul = document.createElement('ul');
child.children.forEach(dc => {
const dli = document.createElement('li');
const da = makeLink(dc.href, dc.title);
da.setAttribute('tabindex','-1');
dli.appendChild(da);
flyul.appendChild(dli);
});
fly.appendChild(flyul);
subli.appendChild(fly);
}

subul.appendChild(subli);
});
sub.appendChild(subul);
li.appendChild(sub);
}
ul.appendChild(li);
});
nav.appendChild(ul);
}

/* Create mobile nav markup (collapsible) */


function buildMobileNav(data){
const mnav = document.getElementById('mobileNav');
const ul = document.createElement('ul');
data.forEach((group,gi) => {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = group.href; a.textContent = group.title;
a.setAttribute('role','menuitem');
const chevron = document.createElement('span'); chevron.className='chev'; chevron.textContent='›';
a.appendChild(chevron);
li.appendChild(a);

if(group.children && group.children.length){


const sub = document.createElement('div'); sub.className='submenu';
const subul = document.createElement('ul');
group.children.forEach(child => {
const subli = document.createElement('li');
const ca = document.createElement('a'); ca.href = child.href; ca.textContent = child.title;
subli.appendChild(ca);
if(child.children && child.children.length){
const flyul = document.createElement('ul');
child.children.forEach(dc => {
const dli = document.createElement('li');
const da = document.createElement('a'); da.href = dc.href; da.textContent = dc.title;
flyul.appendChild(dli); dli.appendChild(da);
});
subli.appendChild(flyul);
}
subul.appendChild(subli);
});
sub.appendChild(subul);
li.appendChild(sub);

// click to toggle submenu on mobile


a.addEventListener('click', (e) => {
e.preventDefault();
li.classList.toggle('open');
});
} else {
// close mobile panel after link selection
a.addEventListener('click', () => closeMobile());
}

ul.appendChild(li);
});
mnav.appendChild(ul);
}

/* toggle mobile */
const mobileBtn = document.getElementById('mobileBtn');
const mobilePanel = document.getElementById('mobilePanel');
const mobileClose = document.getElementById('mobileClose');
function openMobile(){
mobilePanel.classList.add('open');
mobilePanel.setAttribute('aria-hidden','false');
mobileBtn.setAttribute('aria-expanded','true');
}
function closeMobile(){
mobilePanel.classList.remove('open');
mobilePanel.setAttribute('aria-hidden','true');
mobileBtn.setAttribute('aria-expanded','false');
}
mobileBtn.addEventListener('click', openMobile);
mobileClose.addEventListener('click', closeMobile);
mobilePanel.addEventListener('click', (e)=>{ if(e.target===mobilePanel) closeMobile(); });

/* keyboard accessibility for desktop nav */


function addDesktopKeyboard(nav){
nav.querySelectorAll('a[role="menuitem"]').forEach(a => {
a.addEventListener('keydown', (e) => {
const li = a.parentElement;
if(e.key === 'ArrowDown'){
// open submenu if available and focus first item
const submenu = li.querySelector('.submenu');
if(submenu){
li.classList.add('open');
const first = submenu.querySelector('a[role="menuitem"], a');
if(first) first.focus();
}
e.preventDefault();
} else if(e.key === 'Escape'){
li.classList.remove('open');
a.blur();
}
});
});
}

/* smooth scroll for anchors */


document.addEventListener('click', (e) => {
const a = e.target.closest('a');
if(a && a.getAttribute('href') && a.getAttribute('href').startsWith('#')){
const id = a.getAttribute('href').slice(1);
const el = document.getElementById(id);
if(el){ e.preventDefault(); el.scrollIntoView({behavior:'smooth', block:'start'}); closeMobile(); }
}
});

/* dark mode toggle */


const themeToggle = document.getElementById('themeToggle');
function setTheme(t){ document.documentElement.setAttribute('data-theme', t); themeToggle.setAttribute('aria-pressed', t
themeToggle.addEventListener('click', ()=> setTheme(document.documentElement.getAttribute('data-theme') === 'dark' ? 'li
/* restore */
if(localStorage.getItem('site-theme') === 'dark') setTheme('dark');

/* init */
buildDesktopNav(menuData);
buildMobileNav(menuData);
addDesktopKeyboard(document.getElementById('primaryNav'));

/* simple click outside to close desktop submenu */


document.addEventListener('click', (e) => {
const nav = document.getElementById('primaryNav');
if(!nav.contains(e.target)){
nav.querySelectorAll('li.open').forEach(li => li.classList.remove('open'));
}
});
</script>
</body>
</html>
Usage Notes

Usage notes:
- Save the content above to a file named `index.html` and open in a modern browser.
- The menu is generated dynamically: you can change TOP and SUB constants to create more or fewer items.
- Mobile behavior: click the ■ icon to open the mobile menu. Click chevrons to expand submenus on mobile.
- Accessibility: basic keyboard support included; consider enhancing with roving tabindex for full a11y.
- Dark mode persists via localStorage.

You might also like