NavigationSimpleApril 10, 2026
Nav Minimal
A clean top navigation bar with logo (image or text), centered nav links with hover-reveal submenu row, locale switcher, and account/cart icons. Collapses to logo + cart + hamburger on mobile.
View Full Demo →Preview
Source
demo.jsx
import NavMinimal from "./index.jsx";
import { navMinimal } from "../demo-data.js";
import styles from "./demo.module.css";
export default function NavMinimalDemo() {
return (
<div className={styles.wrapper}>
<NavMinimal {...navMinimal} />
<section className={styles.hero}>
<p className={styles.heroEyebrow}>New Collection</p>
<h1 className={styles.heroHeading}>Built for the ride.</h1>
<p className={styles.heroSub}>Precision-engineered frames and components for every terrain.</p>
<a href="#" className={styles.heroCta}>Shop Now</a>
</section>
<section className={styles.section}>
<h2 className={styles.sectionHeading}>Why Novatrex</h2>
<div className={styles.grid}>
{["Lightweight", "Durable", "Performance", "Crafted"].map((item) => (
<div key={item} className={styles.card}>
<span className={styles.cardLabel}>{item}</span>
</div>
))}
</div>
</section>
</div>
);
}
index.jsx
"use client";
import { useState } from "react";
import Link from "next/link";
import styles from "./styles.module.css";
function BagIcon() {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M19 7h-3V6a4 4 0 00-8 0v1H5a1 1 0 00-1 1v11a3 3 0 003 3h10a3 3 0 003-3V8a1 1 0 00-1-1zM10 6a2 2 0 014 0v1h-4V6zm8 13a1 1 0 01-1 1H7a1 1 0 01-1-1V9h2v1a1 1 0 002 0V9h4v1a1 1 0 002 0V9h2v10z" />
</svg>
);
}
function UserIcon() {
return (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" aria-hidden="true">
<circle cx="12" cy="7" r="4" />
<path d="M4 21c0-4 3.6-7 8-7s8 3 8 7" />
</svg>
);
}
function HamburgerIcon() {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" aria-hidden="true">
<line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="18" x2="21" y2="18" />
</svg>
);
}
function CloseIcon() {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" aria-hidden="true">
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
);
}
function LogoContent({ logo }) {
return logo.src ? (
<img src={logo.src} alt={logo.alt ?? ""} className={styles.logoImg} />
) : (
<span className={styles.logoText}>{logo.text}</span>
);
}
export default function NavMinimal({
logo = { text: "Brand" },
navLinks = [],
locales = [],
accountHref = "#",
cartHref = "#",
signInHref = "#",
registerHref = "#",
}) {
const [activeIndex, setActiveIndex] = useState(null);
const [menuOpen, setMenuOpen] = useState(false);
const submenu =
activeIndex !== null ? (navLinks[activeIndex]?.submenu ?? []) : [];
return (
<header
className={styles.header}
onMouseLeave={() => setActiveIndex(null)}
>
<div className={styles.bar}>
{/* Logo */}
<div className={styles.logoWrap}>
<Link href={logo.href ?? "/"} className={styles.logoLink}>
<LogoContent logo={logo} />
</Link>
</div>
{/* Center nav */}
<nav className={styles.nav} aria-label="Main">
<ul className={styles.links}>
{navLinks.map((link, i) => (
<li
key={link.label}
className={`${styles.item} ${activeIndex === i ? styles.itemActive : ""}`}
onMouseEnter={() => setActiveIndex(i)}
>
<Link href={link.href ?? "#"} className={styles.link}>
{link.label}
</Link>
</li>
))}
</ul>
</nav>
{/* Right: locales + icons */}
<div className={styles.right}>
{locales.length > 0 && (
<div className={styles.locales}>
{locales.map((locale) => (
<button
key={locale.label}
className={`${styles.locale} ${locale.active ? styles.localeActive : ""}`}
>
{locale.label}
</button>
))}
</div>
)}
<Link href={accountHref} className={styles.icon} aria-label="Account">
<UserIcon />
</Link>
<Link href={cartHref} className={styles.icon} aria-label="Cart">
<BagIcon />
</Link>
</div>
{/* Mobile right: cart + hamburger */}
<div className={styles.mobileRight}>
<Link href={cartHref} className={styles.icon} aria-label="Cart">
<BagIcon />
</Link>
<button
className={styles.hamburger}
aria-label={menuOpen ? "Close menu" : "Open menu"}
onClick={() => setMenuOpen((o) => !o)}
>
{menuOpen ? <CloseIcon /> : <HamburgerIcon />}
</button>
</div>
</div>
{/* Submenu row */}
{submenu.length > 0 && (
<div className={styles.submenu}>
<div className={styles.submenuBar}>
<div className={styles.submenuSpacer} />
<ul className={styles.submenuLinks}>
{submenu.map((item) => (
<li key={item.label}>
<Link href={item.href ?? "#"} className={styles.submenuLink}>
{item.label}
</Link>
</li>
))}
</ul>
<div className={styles.submenuSpacer} />
</div>
</div>
)}
{/* ======== Mobile menu panel ======== */}
{menuOpen && <div className={styles.backdrop} onClick={() => setMenuOpen(false)} />}
<div className={`${styles.panelClip} ${menuOpen ? styles.panelClipOpen : ""}`}>
<div className={`${styles.panel} ${menuOpen ? styles.panelOpen : ""}`}>
{/* Panel nav */}
<nav className={styles.panelNav}>
{navLinks.map((link) => (
<div key={link.label} className={styles.panelSection}>
<Link
href={link.href ?? "#"}
className={styles.panelLink}
onClick={() => setMenuOpen(false)}
>
{link.label}
</Link>
{link.submenu?.length > 0 && (
<ul className={styles.panelSubmenu}>
{link.submenu.map((sub) => (
<li key={sub.label}>
<Link
href={sub.href ?? "#"}
className={styles.panelSubLink}
onClick={() => setMenuOpen(false)}
>
{sub.label}
</Link>
</li>
))}
</ul>
)}
</div>
))}
</nav>
{/* Panel footer */}
<div className={styles.panelFooter}>
{locales.length > 0 && (
<div className={styles.panelLocales}>
{locales.map((locale, i) => (
<span key={locale.label}>
{i > 0 && <span className={styles.localeSep}> / </span>}
<button
className={`${styles.panelLocale} ${locale.active ? styles.panelLocaleActive : ""}`}
>
{locale.label}
</button>
</span>
))}
</div>
)}
<div className={styles.panelAuth}>
<UserIcon />
<Link href={signInHref} className={styles.authLink}>Sign In</Link>
<span className={styles.authSep}> / </span>
<Link href={registerHref} className={styles.authLink}>Register</Link>
</div>
</div>
</div>
</div>
</header>
);
}
demo.module.css
.wrapper {
background: #f5f5f5;
height: 100vh;
overflow-y: auto;
}
/* Hero */
.hero {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
min-height: 90vh;
padding: 4rem 2rem;
background: #111;
color: #fff;
}
.heroEyebrow {
font-size: 0.68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.12em;
color: #aaa;
margin: 0 0 1.25rem;
}
.heroHeading {
font-size: clamp(2.5rem, 8vw, 6rem);
font-weight: 900;
letter-spacing: -0.03em;
line-height: 1;
margin: 0 0 1.5rem;
}
.heroSub {
font-size: 1rem;
color: #aaa;
max-width: 400px;
line-height: 1.6;
margin: 0 0 2.5rem;
}
.heroCta {
display: inline-block;
padding: 0.75rem 2rem;
background: #fff;
color: #111;
font-size: 0.68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
text-decoration: none;
}
/* Section */
.section {
padding: 5rem 2rem;
background: #fff;
}
.sectionHeading {
font-size: 0.68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #aaa;
text-align: center;
margin: 0 0 3rem;
}
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1px;
background: #e5e5e5;
max-width: 900px;
margin: 0 auto;
}
.card {
background: #fff;
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
}
.cardLabel {
font-size: 0.68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #111;
}
@media (max-width: 600px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
styles.module.css
.header {
--bg: #fff;
--text: #111;
--text-muted: #aaa;
--border: rgba(0, 0, 0, 0.1);
--bar-height: 52px;
--sub-height: 44px;
--font: 0.68rem;
--tracking: 0.07em;
position: sticky;
top: 0;
background: var(--bg);
border-bottom: 1px solid var(--border);
z-index: 100;
}
/* ---- Main bar ---- */
.bar {
display: flex;
align-items: center;
height: var(--bar-height);
padding: 0 1.5rem;
}
/* ---- Logo ---- */
.logoWrap {
flex: 1;
min-width: 0;
}
.logoLink {
display: inline-flex;
align-items: center;
text-decoration: none;
color: var(--text, #111);
}
.logoText {
font-size: 1.3rem;
font-weight: 900;
letter-spacing: -0.025em;
line-height: 1;
}
.logoImg {
height: 26px;
width: auto;
display: block;
}
/* ---- Center nav ---- */
.nav {
flex: 0 0 auto;
}
.links {
display: flex;
align-items: center;
gap: 1.8rem;
list-style: none;
margin: 0;
padding: 0;
}
.link {
font-size: var(--font);
font-weight: 700;
text-transform: uppercase;
letter-spacing: var(--tracking);
color: var(--text, #111);
text-decoration: none;
white-space: nowrap;
}
.itemActive .link {
text-decoration: underline;
text-underline-offset: 3px;
}
/* ---- Right ---- */
.right {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 1.25rem;
}
.locales {
display: flex;
align-items: center;
gap: 0.35rem;
}
.locale {
font-size: var(--font);
font-weight: 700;
text-transform: uppercase;
letter-spacing: var(--tracking);
color: var(--text-muted, #aaa);
background: none;
border: none;
cursor: pointer;
padding: 0;
line-height: 1;
}
.localeActive {
color: var(--text, #111);
}
.icon {
display: flex;
align-items: center;
color: var(--text, #111);
text-decoration: none;
}
/* ---- Mobile right (hidden on desktop) ---- */
.mobileRight {
display: none;
align-items: center;
gap: 1rem;
}
.hamburger {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
background: none;
border: none;
cursor: pointer;
padding: 0;
color: var(--text, #111);
flex-shrink: 0;
}
/* ---- Submenu row ---- */
.submenu {
border-top: 1px solid var(--border);
background: var(--bg);
}
.submenuBar {
display: flex;
align-items: center;
height: var(--sub-height);
padding: 0 1.5rem;
}
.submenuSpacer {
flex: 1;
}
.submenuLinks {
display: flex;
align-items: center;
gap: 1.8rem;
list-style: none;
margin: 0;
padding: 0;
flex: 0 0 auto;
}
.submenuLink {
font-size: var(--font);
font-weight: 700;
text-transform: uppercase;
letter-spacing: var(--tracking);
color: var(--text, #111);
text-decoration: none;
white-space: nowrap;
}
.submenuLink:hover {
text-decoration: underline;
text-underline-offset: 3px;
}
/* ---- Mobile ---- */
@media (max-width: 768px) {
.nav { display: none; }
.right { display: none; }
.mobileRight { display: flex; }
}
/* ======================================================
MOBILE MENU PANEL
====================================================== */
.backdrop {
position: fixed;
inset: 0;
z-index: 199;
}
/* Clip container — hides the panel above the nav during animation */
.panelClip {
position: absolute;
top: var(--bar-height);
left: 0;
width: 100%;
height: calc(100vh - var(--bar-height));
overflow: hidden;
pointer-events: none;
z-index: 200;
border-top: 1px solid var(--border);
}
.panelClipOpen {
pointer-events: auto;
}
/* Panel slides within the clip container */
.panel {
width: 100%;
height: 100%;
background: #fff;
display: flex;
flex-direction: column;
overflow-y: auto;
transform: translateY(-100%);
transition: transform 0.9s cubic-bezier(0.76, 0, 0.24, 1);
}
.panelOpen {
transform: translateY(0);
}
/* ---- Panel nav ---- */
.panelNav {
flex: 1;
}
.panelSection {
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.panelLink {
display: block;
padding: 0.9rem 1.25rem;
font-size: 0.82rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #111;
text-decoration: none;
text-align: center;
}
.panelSubmenu {
list-style: none;
margin: 0;
padding: 0 1.25rem 0.75rem;
text-align: center;
}
.panelSubLink {
display: block;
padding: 0.25rem 0;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: #111;
text-decoration: none;
}
/* ---- Panel footer ---- */
.panelFooter {
flex-shrink: 0;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.panelLocales {
display: flex;
align-items: center;
justify-content: center;
padding: 0.85rem 1.25rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
}
.localeSep {
color: #aaa;
}
.panelLocale {
background: none;
border: none;
cursor: pointer;
padding: 0;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: #aaa;
}
.panelLocaleActive {
color: #111;
}
.panelAuth {
display: flex;
align-items: center;
justify-content: center;
gap: 0.35rem;
padding: 0.85rem 1.25rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
color: #111;
}
.authLink {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: #111;
text-decoration: none;
}
.authSep {
font-size: 0.7rem;
color: #aaa;
}