All components

Header 01

Header 01 is a React and Tailwind CSS layout component from Eldora UI, licensed MIT. Copy it and paste it into your project.

Eldora UI MIT layout

What it is

Header 01 is a React and Tailwind CSS layout component from Eldora UI, licensed MIT. Copy it and paste it into your project.

How to use it

Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.

Open in the catalogue

Five free copies a month. No card.

Needs

npm i motion/react lucide-react next-themes

Licence

This component comes from Eldora UI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.

Header 01

registry/blocks/header-01/page.tsx
import { Navbar } from "@/registry/blocks/header-01/components/navbar"

export default function Page() {
  return (
    <div className="min-h-svh w-full">
      <Navbar />
      <main className="flex-1">
        <section className="flex h-screen items-center justify-center">
          <h1 className="text-4xl font-bold">Hero</h1>
        </section>
        <section className="flex h-screen items-center justify-center">
          <h1 className="text-4xl font-bold">Section</h1>
        </section>
      </main>
    </div>
  )
}
registry/blocks/header-01/components/navbar.tsx
"use client"

import { useEffect, useState } from "react"
import Link from "next/link"
import { Menu, X } from "lucide-react"
import { AnimatePresence, motion, useScroll } from "motion/react"

import { cn } from "@/lib/utils"
import { Icons } from "@/components/icons"
import { NavMenu } from "@/registry/blocks/header-01/components/nav-menu"
import { ThemeToggle } from "@/registry/blocks/header-01/components/theme-toggle"
import { navLinks } from "@/registry/blocks/header-01/lib/nav-links"

const INITIAL_WIDTH = "70rem"
const MAX_WIDTH = "800px"

// Animation variants
const overlayVariants = {
  hidden: { opacity: 0 },
  visible: { opacity: 1 },
  exit: { opacity: 0 },
}

const drawerVariants = {
  hidden: { opacity: 0, y: 100 },
  visible: {
    opacity: 1,
    y: 0,
    rotate: 0,
    transition: {
      type: "spring" as const,
      damping: 15,
      stiffness: 200,
      staggerChildren: 0.03,
    },
  },
  exit: {
    opacity: 0,
    y: 100,
    transition: { duration: 0.1 },
  },
}

const drawerMenuContainerVariants = {
  hidden: { opacity: 0 },
  visible: { opacity: 1 },
}

const drawerMenuVariants = {
  hidden: { opacity: 0 },
  visible: { opacity: 1 },
}

export function Navbar() {
  const { scrollY } = useScroll()
  const [hasScrolled, setHasScrolled] = useState(false)
  const [isDrawerOpen, setIsDrawerOpen] = useState(false)
  const [activeSection, setActiveSection] = useState("hero")

  useEffect(() => {
    const handleScroll = () => {
      const sections = navLinks.map((item) => item.href.substring(1))

      for (const section of sections) {
        const element = document.getElementById(section)
        if (element) {
          const rect = element.getBoundingClientRect()
          if (rect.top <= 150 && rect.bottom >= 150) {
            setActiveSection(section)
            break
          }
        }
      }
    }

    window.addEventListener("scroll", handleScroll)
    handleScroll()

    return () => window.removeEventListener("scroll", handleScroll)
  }, [])

  useEffect(() => {
    const unsubscribe = scrollY.on("change", (latest) => {
      setHasScrolled(latest > 10)
    })
    return unsubscribe
  }, [scrollY])

  const toggleDrawer = () => setIsDrawerOpen((prev) => !prev)
  const handleOverlayClick = () => setIsDrawerOpen(false)

  return (
    <header
      className={cn(
        "sticky z-50 mx-4 flex justify-center transition-all duration-300 md:mx-0",
        hasScrolled ? "top-6" : "top-4 mx-0"
      )}
    >
      <motion.div
        initial={{ width: INITIAL_WIDTH }}
        animate={{ width: hasScrolled ? MAX_WIDTH : INITIAL_WIDTH }}
        transition={{ duration: 0.3, ease: [0.25, 0.1, 0.25, 1] }}
      >
        <div
          className={cn(
            "mx-auto max-w-7xl rounded-2xl transition-all duration-300 xl:px-0",
            hasScrolled
              ? "border-border bg-background/75 border px-2 backdrop-blur-lg"
              : "px-7 shadow-none"
          )}
        >
          <div className="flex h-[56px] items-center justify-between p-4">
            <Link href="/" className="flex items-center gap-3">
              <Icons.logo className="-mt-1 size-4 md:size-6" />
              <p className="text-primary ml-1 text-lg font-semibold">
                EldoraUI
              </p>
            </Link>

            <NavMenu />

            <div className="flex shrink-0 flex-row items-center gap-1 md:gap-3">
              <div className="flex items-center space-x-6">
                <Link
                  className="text-primary-foreground dark:text-cyan-500-foreground hidden h-8 w-fit items-center justify-center rounded-full border border-white/[0.12] bg-cyan-500 px-4 text-sm font-normal tracking-wide shadow-[inset_0_1px_2px_rgba(255,255,255,0.25),0_3px_3px_-1.5px_rgba(16,24,40,0.06),0_1px_1px_rgba(16,24,40,0.08)] md:flex"
                  href="#"
                >
                  Try for free
                </Link>
              </div>
              <ThemeToggle />
              <button
                className="border-border flex size-8 cursor-pointer items-center justify-center rounded-md border md:hidden"
                onClick={toggleDrawer}
              >
                {isDrawerOpen ? (
                  <X className="size-5" />
                ) : (
                  <Menu className="size-5" />
                )}
              </button>
            </div>
          </div>
        </div>
      </motion.div>

      {/* Mobile Drawer */}
      <AnimatePresence>
        {isDrawerOpen && (
          <>
            <motion.div
              className="fixed inset-0 bg-black/50 backdrop-blur-sm"
              initial="hidden"
              animate="visible"
              exit="exit"
              variants={overlayVariants}
              transition={{ duration: 0.2 }}
              onClick={handleOverlayClick}
            />

            <motion.div
              className="bg-background border-border fixed inset-x-0 bottom-3 mx-auto w-[95%] rounded-xl border p-4 shadow-lg"
              initial="hidden"
              animate="visible"
              exit="exit"
              variants={drawerVariants}
            >
              {/* Mobile menu content */}
              <div className="flex flex-col gap-4">
                <div className="flex items-center justify-between">
                  <Link href="/" className="flex items-center gap-3">
                    <Icons.logo className="size-7 md:size-10" />
                    <p className="text-primary text-lg font-semibold">
                      SkyAgent
                    </p>
                  </Link>
                  <button
                    onClick={toggleDrawer}
                    className="border-border cursor-pointer rounded-md border p-1"
                  >
                    <X className="size-5" />
                  </button>
                </div>

                <motion.ul
                  className="border-border mb-4 flex flex-col rounded-md border text-sm"
                  variants={drawerMenuContainerVariants}
                >
                  <AnimatePresence>
                    {navLinks.map((item) => (
                      <motion.li
                        key={item.id}
                        className="border-border border-b p-2.5 last:border-b-0"
                        variants={drawerMenuVariants}
                      >
                        <a
                          href={item.href}
                          onClick={(e) => {
                            e.preventDefault()
                            const element = document.getElementById(
                              item.href.substring(1)
                            )
                            element?.scrollIntoView({ behavior: "smooth" })
                            setIsDrawerOpen(false)
                          }}
                          className={`hover:text-primary/80 underline-offset-4 transition-colors ${
                            activeSection === item.href.substring(1)
                              ? "text-primary font-medium"
                              : "text-primary/60"
                          }`}
                        >
                          {item.name}
                        </a>
                      </motion.li>
                    ))}
                  </AnimatePresence>
                </motion.ul>

                {/* Action buttons */}
                <div className="flex flex-col gap-2">
                  <Link
                    href="#"
                    className="text-primary-foreground dark:text-cyan-500-foreground flex h-8 w-full items-center justify-center rounded-full border border-white/[0.12] bg-cyan-500 px-4 text-sm font-normal tracking-wide shadow-[inset_0_1px_2px_rgba(255,255,255,0.25),0_3px_3px_-1.5px_rgba(16,24,40,0.06),0_1px_1px_rgba(16,24,40,0.08)] transition-all ease-out hover:bg-cyan-500/80 active:scale-95"
                  >
                    Try for free
                  </Link>
                </div>
              </div>
            </motion.div>
          </>
        )}
      </AnimatePresence>
    </header>
  )
}
registry/blocks/header-01/components/nav-menu.tsx
"use client"

import React, { useRef, useState } from "react"
import { motion } from "motion/react"

import { navLinks } from "@/registry/blocks/header-01/lib/nav-links"

interface NavItem {
  id: number
  name: string
  href: string
}

const navs: NavItem[] = [...navLinks]

export function NavMenu() {
  const ref = useRef<HTMLUListElement>(null)
  const [left, setLeft] = useState(0)
  const [width, setWidth] = useState(0)
  const [isReady, setIsReady] = useState(false)
  const [activeSection, setActiveSection] = useState("hero")
  const [isManualScroll, setIsManualScroll] = useState(false)

  React.useEffect(() => {
    // Initialize with first nav item
    const firstItem = ref.current?.querySelector(
      `[href="#${navs[0].href.substring(1)}"]`
    )?.parentElement
    if (firstItem) {
      const rect = firstItem.getBoundingClientRect()
      setLeft(firstItem.offsetLeft)
      setWidth(rect.width)
      setIsReady(true)
    }
  }, [])

  React.useEffect(() => {
    const handleScroll = () => {
      // Skip scroll handling during manual click scrolling
      if (isManualScroll) return

      const sections = navs.map((item) => item.href.substring(1))

      // Find the section closest to viewport top
      let closestSection = sections[0]
      let minDistance = Infinity

      for (const section of sections) {
        const element = document.getElementById(section)
        if (element) {
          const rect = element.getBoundingClientRect()
          const distance = Math.abs(rect.top - 100) // Offset by 100px to trigger earlier
          if (distance < minDistance) {
            minDistance = distance
            closestSection = section
          }
        }
      }

      // Update active section and nav indicator
      setActiveSection(closestSection)
      const navItem = ref.current?.querySelector(
        `[href="#${closestSection}"]`
      )?.parentElement
      if (navItem) {
        const rect = navItem.getBoundingClientRect()
        setLeft(navItem.offsetLeft)
        setWidth(rect.width)
      }
    }

    window.addEventListener("scroll", handleScroll)
    handleScroll() // Initial check
    return () => window.removeEventListener("scroll", handleScroll)
  }, [isManualScroll])

  const handleClick = (
    e: React.MouseEvent<HTMLAnchorElement>,
    item: NavItem
  ) => {
    e.preventDefault()

    const targetId = item.href.substring(1)
    const element = document.getElementById(targetId)

    if (element) {
      // Set manual scroll flag
      setIsManualScroll(true)

      // Immediately update nav state
      setActiveSection(targetId)
      const navItem = e.currentTarget.parentElement
      if (navItem) {
        const rect = navItem.getBoundingClientRect()
        setLeft(navItem.offsetLeft)
        setWidth(rect.width)
      }

      // Calculate exact scroll position
      const elementPosition = element.getBoundingClientRect().top
      const offsetPosition = elementPosition + window.pageYOffset - 100 // 100px offset

      // Smooth scroll to exact position
      window.scrollTo({
        top: offsetPosition,
        behavior: "smooth",
      })

      // Reset manual scroll flag after animation completes
      setTimeout(() => {
        setIsManualScroll(false)
      }, 500) // Adjust timing to match scroll animation duration
    }
  }

  return (
    <div className="hidden w-full md:block">
      <ul
        className="relative mx-auto flex h-11 w-fit items-center justify-center rounded-full px-2"
        ref={ref}
      >
        {navs.map((item) => (
          <li
            key={item.id}
            className={`z-10 flex h-full cursor-pointer items-center justify-center px-4 py-2 text-sm font-medium transition-colors duration-200 ${
              activeSection === item.href.substring(1)
                ? "text-primary"
                : "text-primary/60 hover:text-primary"
            } tracking-tight`}
          >
            <a href={item.href} onClick={(e) => handleClick(e, item)}>
              {item.name}
            </a>
          </li>
        ))}
        {isReady && (
          <motion.li
            animate={{ left, width }}
            transition={{ type: "spring", stiffness: 400, damping: 30 }}
            className="bg-accent/60 border-border absolute inset-0 my-1.5 rounded-full border"
          />
        )}
      </ul>
    </div>
  )
}
registry/blocks/header-01/components/theme-toggle.tsx
"use client"

import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"

import { Button } from "@/components/ui/button"

export function ThemeToggle() {
  const { theme, setTheme } = useTheme()

  return (
    <Button
      variant="outline"
      size="icon"
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
      className="h-8 w-8 cursor-pointer rounded-full"
    >
      <Sun className="text-primary h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
      <Moon className="text-primary absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
      <span className="sr-only">Toggle theme</span>
    </Button>
  )
}
registry/blocks/header-01/lib/nav-links.ts
export const navLinks = [
  { id: 1, name: "Home", href: "/" },
  { id: 2, name: "How it Works", href: "/" },
  { id: 3, name: "Features", href: "/" },
  { id: 4, name: "Pricing", href: "/" },
] as const

Similar components