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 →
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;
}

May 4, 2026

NAVIGATION

Nav Slide Panel

A minimal header that slides in from the top on load. Desktop shows logo, nav links, language switcher, and underlined contact CTA. Mobile collapses to logo + menu toggle with a full-screen white panel featuring large stagger-animated nav links, office locations, language selector, and copyright.

Apr 27, 2026

NAVIGATION

Nav Agency Grid

A fixed agency-style navigation bar using a 4-column CSS grid with staggered GSAP entrance animations, current-page indicator with animated dash, scroll-direction hide/show, desktop hover dropdown, and a full-screen mobile menu with large typography.

Apr 24, 2026

NAVIGATION

Nav Glassmorphic

A floating glassmorphic navigation bar with frosted semi-transparent background, centered nav links, locale selector, and outlined CTA button. Collapses to logo + hamburger on mobile with an expanding panel featuring nav links, social links, and email.