NavigationSimpleApril 9, 2026

Nav Bar

Fixed transparent navbar with name/role on the left, flip-text navigation links in the center, and a live EST clock on the right. Hover reveals a growing underline and a vertical text flip. Active link state is driven by URL hash.

View Full Demo →
demo.jsx
import NavBar from "./index.jsx";
import styles from "./demo.module.css";

export default function NavBarDemo() {
  return (
    <div className={styles.demo}>
      <NavBar />
      <div className={styles.hero}>
        <p className={styles.label}>Est. 2024 — New York</p>
        <h1 className={styles.heading}>No. 5 Studio</h1>
        <p className={styles.sub}>Design &amp; Development</p>
      </div>
      <div className={styles.footer}>
        <span>Selected Work</span>
        <span>Currently Available</span>
      </div>
    </div>
  );
}
index.jsx
'use client';
import { useState, useEffect } from "react";
import Link from "next/link";
import styles from "./styles.module.css";

export default function NavBar({ name = "Kevin Davis", role = "Web Developer", navItems = ["Work", "About", "Contact"] }) {
  const [time, setTime] = useState("");
  const [activeHash, setActiveHash] = useState("");

  // --- Clock logic ---
  useEffect(() => {
    const updateClock = () => {
      const now = new Date();
      const options = {
        hour: "2-digit",
        minute: "2-digit",
        second: "2-digit",
        hour12: true,
        timeZone: "America/New_York",
      };
      const formatted = now.toLocaleTimeString("en-US", options).replace(" ", " ");
      setTime(formatted);
    };
    updateClock();
    const timer = setInterval(updateClock, 1000);
    return () => clearInterval(timer);
  }, []);

  // --- Hash tracking for active underline ---
  useEffect(() => {
    const handleHashChange = () => setActiveHash(window.location.hash);
    window.addEventListener("hashchange", handleHashChange);
    handleHashChange();
    return () => window.removeEventListener("hashchange", handleHashChange);
  }, []);

  return (
    <nav className={styles.navbar}>
      {/* Left: Name and role */}
      <div className={styles.left}>
        <h3 className={styles.name}>{name}</h3>
        <span className={styles.role}>{role}</span>
      </div>

      {/* Middle: Navigation links */}
      <ul className={styles.links}>
        {navItems.map((item) => {
          const hash = `#${item.toLowerCase()}`;
          return (
            <li key={item}>
              <Link
                href={hash}
                onClick={() => setActiveHash(hash)}
                className={`${styles.btnLineHover} ${activeHash === hash ? styles.active : ""}`}
              >
                <span className={styles.btnText}>
                  <span>{item}</span>
                  <span>{item}</span>
                </span>
              </Link>
            </li>
          );
        })}
      </ul>

      {/* Right: Live EST Clock */}
      <div className={styles.clock}>
        {time && <span>&nbsp;{time}&nbsp;EST</span>}
      </div>
    </nav>
  );
}
demo.module.css
.demo {
  position: relative;
  min-height: 100vh;
  background: #0a0a0a;
  display: flex;
  flex-direction: column;
  /* Contains fixed-position children (NavBar) within this element */
  transform: translateZ(0);
}

.hero {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 6rem 2rem 4rem;
  gap: 1rem;
}

.label {
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.35);
}

.heading {
  font-size: clamp(3.5rem, 10vw, 9rem);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 0.95;
  color: #ffffff;
}

.sub {
  font-size: 1rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.4);
}

.footer {
  display: flex;
  justify-content: space-between;
  padding: 1.5rem 2rem;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.25);
}
styles.module.css
.navbar {
  --fg: #ffffff;

  position: fixed;
  top: 1.25rem;
  left: 2rem;
  right: 2rem;

  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  z-index: 1000;

  opacity: 0;
  animation: fadeIn 0.8s ease forwards;
  color: var(--fg);
  font-weight: 600;
  letter-spacing: 0.01em;
  background: transparent;
}

/* left: name + role */
.left {
  display: flex;
  align-items: baseline;
  gap: 2rem;
}

.name,
.role {
  font-size: 1.05rem;
  font-weight: 400;
  color: var(--fg);
  opacity: 0.8;
}

/* middle: nav links */
.links {
  display: flex;
  justify-content: center;
  gap: .5rem;
  list-style: none;
  margin: 0;
  padding-left: 30rem;
}

.links a {
  text-decoration: none;
  color: var(--fg);
  font-size: 1.1rem;
  font-weight: 400;
  transition: opacity 0.2s ease;
}

/* ───────────────────────────────
   Flip + underline
   ─────────────────────────────── */
.btnLineHover {
  position: relative;
  display: inline-block;
  overflow: visible;
  color: var(--fg);
  text-decoration: none;
  transition: color 0.3s ease;
  padding-bottom: 3px;
}

/* underline: single line grows left → right */
.btnLineHover::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background-color: var(--fg);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.45s cubic-bezier(0.65, 0, 0.35, 1);
}

/* reveal underline on hover */
.btnLineHover:hover::after {
  transform: scaleX(1);
}

/* stacked text container */
.btnText {
  display: inline-block;
  position: relative;
  overflow: hidden;
  line-height: 1.2;
}

/* both spans (top + bottom) occupy same space */
.btnText span {
  display: block;
  transition: transform 0.45s cubic-bezier(0.77, 0, 0.175, 1),
              opacity 0.45s ease;
}

/* hover: top text goes up/fades, bottom text slides in */
.btnLineHover:hover .btnText span:first-child {
  transform: translateY(-100%);
  opacity: 0;
}

.btnLineHover:hover .btnText span:last-child {
  transform: translateY(-100%);
  opacity: 1;
}

/* start: bottom text hidden below */
.btnText span:last-child {
  position: absolute;
  left: 0;
  top: 100%;
  opacity: 0;
}

/* right: live clock */
.clock {
  font-size: 1.1rem;
  font-weight: 400;
  text-transform: uppercase;
  white-space: nowrap;
  text-align: right;
}

/* keep underline visible on active link */
.btnLineHover.active::after {
  transform: scaleX(1);
}

/* fade-in animation for navbar */
@keyframes fadeIn {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}

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.