Tawny
All components

Navigation

Tawny Navbar

Scroll-aware site chrome with brand mark, desktop links, and mobile menu.

components/navbar

Source

Copy the files below into your project.

apps/web/components/navbar.tsx
'use client'

import { useState, useEffect } from 'react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { Menu, X } from 'lucide-react'
import { Logo } from '@/components/logo'
import { cn } from '@tawny/ui/lib/utils'

const navLinks = [
  { label: 'Designs', href: '/designs' },
  { label: 'Components', href: '/components' },
  { label: 'Docs', href: '/docs' },
  { label: 'Pricing', href: '/pricing' },
]

function isNavActive(pathname: string, href: string) {
  return pathname === href || pathname.startsWith(`${href}/`)
}

export function Navbar() {
  const [scrolled, setScrolled] = useState(false)
  const [mobileOpen, setMobileOpen] = useState(false)
  const pathname = usePathname()

  useEffect(() => {
    const handleScroll = () => setScrolled(window.scrollY > 16)
    window.addEventListener('scroll', handleScroll, { passive: true })
    return () => window.removeEventListener('scroll', handleScroll)
  }, [])

  // Close mobile menu on route change
  useEffect(() => setMobileOpen(false), [pathname])

  return (
    <header
      className={cn(
        'fixed top-0 left-0 right-0 z-50 transition-all duration-300',
        scrolled
          ? 'bg-background/80 backdrop-blur-xl border-b border-white/8 shadow-[0_1px_0_rgba(255,255,255,0.04)]'
          : // Mobile always gets a subtle scrim so the logo/links stay readable over the hero;
            // desktop stays fully transparent until scroll.
            'bg-background/35 backdrop-blur-md border-b border-white/5 md:bg-transparent md:backdrop-blur-none md:border-transparent'
      )}
    >
      <div className="mx-auto max-w-7xl px-6 h-14 flex items-center justify-between">
        {/* Logo */}
        <Link href="/" className="flex items-center gap-2.5 group" aria-label="Tawny home">
          <Logo className="size-8 rounded-md group-hover:opacity-80 transition-opacity" />
          <span className="font-serif text-xl italic tracking-tight text-foreground">Tawny</span>
        </Link>

        {/* Desktop nav */}
        <nav className="hidden md:flex items-center gap-1" aria-label="Main navigation">
          {navLinks.map((link) => (
            <Link
              key={link.href}
              href={link.href}
              className={cn(
                'px-3 py-1.5 rounded-md text-sm transition-colors',
                isNavActive(pathname, link.href)
                  ? 'text-foreground bg-white/8'
                  : 'text-muted-foreground hover:text-foreground hover:bg-white/5'
              )}
            >
              {link.label}
            </Link>
          ))}
        </nav>

        {/* CTA */}
        <div className="hidden md:flex items-center gap-3">
          <Link
            href="/pricing"
            className="inline-flex items-center gap-1.5 px-4 py-1.5 rounded-full bg-foreground text-background text-sm font-medium hover:opacity-85 transition-opacity"
          >
            Get access
          </Link>
        </div>

        {/* Mobile toggle */}
        <button
          className="md:hidden p-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-white/5 transition-colors"
          aria-label={mobileOpen ? 'Close menu' : 'Open menu'}
          aria-expanded={mobileOpen}
          onClick={() => setMobileOpen((v) => !v)}
        >
          {mobileOpen ? <X size={18} /> : <Menu size={18} />}
        </button>
      </div>

      {/* Mobile menu */}
      {mobileOpen && (
        <div className="md:hidden bg-background/95 backdrop-blur-xl border-b border-white/8 px-6 pb-5 pt-2 flex flex-col gap-1">
          {navLinks.map((link) => (
            <Link
              key={link.href}
              href={link.href}
              className={cn(
                'px-3 py-2 rounded-md text-sm transition-colors',
                isNavActive(pathname, link.href)
                  ? 'text-foreground bg-white/8'
                  : 'text-muted-foreground hover:text-foreground hover:bg-white/5'
              )}
            >
              {link.label}
            </Link>
          ))}
          <div className="mt-2 pt-3 border-t border-white/8">
            <Link
              href="/pricing"
              className="flex items-center justify-center px-4 py-2 rounded-full bg-foreground text-background text-sm font-medium hover:opacity-85 transition-opacity"
            >
              Get access
            </Link>
          </div>
        </div>
      )}
    </header>
  )
}
apps/web/components/logo.tsx
import { cn } from '@tawny/ui/lib/utils'

/**
 * Tawny brand mark — a double crescent inside a rounded field.
 *
 * Uses the `tawny` / `tawny-foreground` theme tokens so the mark tracks
 * the same warm accent used elsewhere on the site (e.g. the hero wordmark dot).
 */
export function Logo({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 260 260"
      fill="none"
      aria-hidden="true"
      className={cn('shrink-0', className)}
    >
      <rect x="30" y="30" width="200" height="200" rx="44" className="fill-tawny" />
      <path
        d="M130 75 A55 55 0 0 1 130 185 A70 70 0 0 0 130 75 Z"
        className="fill-tawny-foreground"
      />
      <path
        d="M130 75 A55 55 0 0 0 130 185 A70 70 0 0 1 130 75 Z"
        className="fill-tawny-foreground"
        opacity="0.4"
      />
    </svg>
  )
}