A React navbar that resizes on scroll, with animated elements inside. Built with Motion and Tailwind CSS.
API TOOLCHAIN
Build with a toolchain that waste the entire CI quota, from developer-first design to polished artifact, and then throw it away.
import { Smth } from "@smth/cjs";
const smth = new Smth({
reason:"WHY IS STILL CJS????",
});Rest, Soap, It doesn't matter tbh
Make run some stuff across the stack to tell collegues you're busy.
USE YOUR APIS WITH
Do you have still tokens left? I suggest you chrome MCP Server. Thanks me later.
"use client";
import localFont from "next/font/local";
import { useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { StretchNavbar } from "./stretch-navbar";
const hairline = "color-mix(in oklab, #171717 9%, white)";
function StackMark({ className }: { className?: string }) {
return (
<svg
aria-hidden
className={className}
fill="none"
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
>
<title>Stacked documents</title>
<rect
fill="#F7F7F6"
height="18"
rx="3"
stroke="#D8D8D6"
width="18"
x="7"
y="10"
/>
<rect
fill="#FBFBFA"
height="18"
rx="3"
stroke="#C9C9C6"
width="18"
x="5"
y="7"
/>
<rect
fill="white"
height="18"
rx="3"
stroke="#BDBDBA"
width="18"
x="3"
y="4"
/>
<path
d="M8 11.5h8M8 14.5h10M8 17.5h6"
stroke="#A3A3A0"
strokeLinecap="round"
strokeWidth="1.2"
/>
</svg>
);
}
function CheckMark() {
return (
<svg
aria-hidden
className="size-3.5 text-[#2F9E44]"
fill="none"
viewBox="0 0 16 16"
>
<title>Complete</title>
<circle cx="8" cy="8" fill="currentColor" fillOpacity="0.12" r="7" />
<path
d="M5 8.2 7.1 10.3 11.2 5.8"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
</svg>
);
}
function DesignGraphic() {
return (
<div className="relative mx-auto w-[min(100%,220px)] rounded-[10px] border border-[#E6E6E4] bg-white p-3 shadow-[0_8px_24px_rgb(16_16_16/0.06)]">
<div className="mb-2.5 flex items-center gap-1">
<span className="size-1.5 rounded-full bg-[#E4E4E1]" />
<span className="size-1.5 rounded-full bg-[#E4E4E1]" />
<span className="size-1.5 rounded-full bg-[#E4E4E1]" />
</div>
<pre className="overflow-hidden font-mono text-[9px] leading-[1.55] tracking-tight">
<code>
<span className="text-[#8A8A86]">import</span>
<span className="text-[#3F3F3C]"> {"{ Smth }"} </span>
<span className="text-[#8A8A86]">from</span>
<span className="text-[#5B6B8A]"> "@smth/cjs"</span>
<span className="text-[#8A8A86]">;</span>
{"\n\n"}
<span className="text-[#8A8A86]">const</span>
<span className="text-[#3F3F3C]"> smth = </span>
<span className="text-[#8A8A86]">new</span>
<span className="text-[#3F3F3C]"> Smth({"{"}</span>
{"\n"}
<span className="text-[#3F3F3C]">{" "}reason:</span>
<span className="text-[#6B5B8A]">"WHY IS STILL CJS????"</span>
<span className="text-[#3F3F3C]">,</span>
{"\n"}
<span className="text-[#3F3F3C]">{"}"});</span>
</code>
</pre>
<div className="mt-3 flex justify-end">
<span className="rounded-md border border-[#E2E2DF] bg-[#F7F7F5] px-2 py-1 font-medium font-sans text-[#3A3A38] text-[8px] tracking-[0.04em]">
CREATE SDK
</span>
</div>
</div>
);
}
function DistributeGraphic() {
return (
<div className="mx-auto flex w-[min(100%,240px)] flex-col items-center">
<div className="grid w-full grid-cols-3 gap-2">
{[
{ label: "Kubernetes" },
{ label: "Terraform" },
{ label: "ArgoCD" },
].map((item) => (
<div
className="flex flex-col items-center gap-1 rounded-md border border-[#E6E6E4] bg-white px-1.5 py-2 shadow-[0_4px_12px_rgb(16_16_16/0.04)]"
key={item.label}
>
<CheckMark />
<span className="font-medium font-sans text-[#5C5C58] text-[8px] tracking-[0.04em]">
{item.label}
</span>
</div>
))}
</div>
<svg aria-hidden className="h-10 w-full" fill="none" viewBox="0 0 240 40">
<title>Distribution flow</title>
<path d="M40 0v10C40 22 90 22 120 22" stroke="#D4D4D1" />
<path d="M120 0v22" stroke="#D4D4D1" />
<path d="M200 0v10C200 22 150 22 120 22" stroke="#D4D4D1" />
<path d="M120 22v18" stroke="#D4D4D1" />
</svg>
<div className="rounded-full border border-[#E6E6E4] bg-white px-4 py-1.5 font-sans font-semibold text-[#de2323] text-[10px] tracking-[0.12em]">
FAILED
</div>
</div>
);
}
function McpGraphic() {
return (
<div className="mx-auto flex w-[min(100%,250px)] flex-col items-center gap-2">
<p className="flex items-center gap-1.5 font-medium font-sans text-[#8A8A86] text-[9px] tracking-[0.14em]">
<svg aria-hidden className="size-3 text-[#C4A35A]" viewBox="0 0 12 12">
<title>Highlight</title>
<path
d="M6 1.2 6.7 4.4 10 5 6.7 5.6 6 8.8 5.3 5.6 2 5l3.3-.6z"
fill="currentColor"
/>
</svg>
USE YOUR APIS WITH
</p>
<div className="flex items-center gap-1.5 rounded-full border border-[#E6E6E4] bg-white p-1.5 shadow-[0_8px_20px_rgb(16_16_16/0.05)]">
<span className="flex items-center gap-1 rounded-full bg-[#EEF3FB] px-2.5 py-1 font-medium font-sans text-[#355A8A] text-[9px] tracking-[0.06em]">
<span className="size-1.5 rotate-45 bg-[#4C7AB5]" />
MCP
</span>
<span className="flex items-center gap-1 rounded-full bg-[#F3F0EA] px-2.5 py-1 font-medium font-sans text-[#6A5A3A] text-[9px] tracking-[0.06em]">
<svg aria-hidden className="size-3" fill="none" viewBox="0 0 12 12">
<title>Agents</title>
<circle cx="4" cy="4" r="1.4" stroke="#8A7350" strokeWidth="1.1" />
<circle
cx="8.2"
cy="4"
r="1.4"
stroke="#8A7350"
strokeWidth="1.1"
/>
<path
d="M2.2 9c.4-1.2 1.4-2 2.6-2s2.2.8 2.6 2M7 7.2c.9-.4 2-.3 2.8.5"
stroke="#8A7350"
strokeLinecap="round"
strokeWidth="1.1"
/>
</svg>
AGENTS
</span>
<span className="flex items-center gap-1 rounded-full bg-[#EEF6F0] px-2.5 py-1 font-medium font-sans text-[#2F6B45] text-[9px] tracking-[0.06em]">
<svg aria-hidden className="size-3" fill="none" viewBox="0 0 12 12">
<title>Chat</title>
<path
d="M2 3.2h8v5.2H5.2L2.4 10.2V3.2z"
stroke="#3D8A58"
strokeLinejoin="round"
strokeWidth="1.1"
/>
</svg>
CHAT
</span>
</div>
</div>
);
}
const features = [
{
body: "Rest, Soap, It doesn't matter tbh",
graphic: <DesignGraphic />,
index: "01",
title: "Design nonsense APIs",
},
{
body: "Make run some stuff across the stack to tell collegues you're busy.",
graphic: <DistributeGraphic />,
index: "02",
title: "Distribute API randomly",
},
{
body: "Do you have still tokens left? I suggest you chrome MCP Server. Thanks me later.",
graphic: <McpGraphic />,
index: "03",
title: "Develop MCP servers",
},
] as const;
function ToolchainLanding({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
className={cn(
"relative min-h-[1280px] bg-white font-sans text-[#171717]",
"selection:bg-[#171717] selection:text-white",
className
)}
{...props}
>
<div
aria-hidden
className="pointer-events-none absolute inset-0 opacity-30"
style={{
backgroundImage: `linear-gradient(to right, ${hairline} 1px, transparent 1px), linear-gradient(to bottom, ${hairline} 1px, transparent 1px)`,
backgroundSize: "72px 72px",
}}
/>
<section className="relative flex flex-col items-center px-6 pt-24 pb-0">
<h1
className="max-w-[20ch] text-center text-[1.7rem] leading-[1.2] tracking-[-0.03em] md:max-w-none md:text-[2.15rem]"
>
Build worse products.
<br />
SDKs for users, MCP for LLMs.
<br />
2M tokens gone in a day.
</h1>
<div className="mt-10 flex flex-col items-center">
<div className="grid size-11 place-items-center rounded-[10px] border border-[#E4E4E1] bg-white shadow-[0_4px_14px_rgb(16_16_16/0.06)]">
<StackMark className="size-8" />
</div>
<div
aria-hidden
className="h-14 w-px"
style={{
background:
"linear-gradient(180deg, #6BA4E8 0%, #8B7CF6 42%, #E08A4C 100%)",
}}
/>
</div>
</section>
<div className="relative mx-auto flex w-full max-w-5xl flex-col items-center px-4 md:px-6">
<section
aria-labelledby="toolchain-heading"
className="w-full border-x border-t bg-white"
style={{ borderColor: hairline }}
>
<div
className="grid gap-8 px-5 py-10 md:grid-cols-2 md:items-start md:gap-12 md:px-8 md:py-12"
style={{ borderBottom: `1px solid ${hairline}` }}
>
<div>
<p className="mb-4 inline-flex rounded-full border border-[#E6E6E4] bg-[#F7F7F5] px-3 py-1 font-medium font-sans text-[#6E6E6A] text-[10px] tracking-[0.14em]">
API TOOLCHAIN
</p>
<h2
className="max-w-[12ch] text-[2rem] leading-[1.12] tracking-[-0.03em] md:text-[2.45rem]"
id="toolchain-heading"
>
From design to trashcan
</h2>
</div>
<p className="max-w-[42ch] font-sans text-[#6A6A66] text-[15px] leading-relaxed md:mt-8 md:justify-self-end">
Build with a toolchain that waste the entire CI quota, from
developer-first design to polished artifact, and then throw it
away.
</p>
</div>
<div className="grid md:grid-cols-3">
{features.map((feature, index) => (
<article
className={cn(
"flex flex-col border-[#E8E8E5] border-b px-5 py-8 md:px-7 md:py-10",
index < features.length - 1 && "md:border-r"
)}
key={feature.index}
>
<span className="font-medium font-sans text-[#B0B0AB] text-[11px] tabular-nums tracking-[0.08em]">
{feature.index}
</span>
<div className="flex min-h-[168px] items-center py-6">
{feature.graphic}
</div>
<h3
className="mt-1 text-[1.35rem] leading-tight tracking-[-0.02em]"
>
{feature.title}
</h3>
<p className="mt-3 max-w-[36ch] font-sans text-[#6A6A66] text-[13.5px] leading-relaxed">
{feature.body}
</p>
</article>
))}
</div>
</section>
</div>
<div className="h-24" />
</div>
);
}
const StretchNavbarDemo: React.FC = () => {
const scrollRef = useRef<HTMLDivElement>(null);
const [isScrollReady, setIsScrollReady] = useState(false);
useEffect(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
if (scrollRef.current) {
setIsScrollReady(true);
}
});
});
}, []);
return (
<div className="light relative flex h-[700px] flex-col justify-start overflow-hidden bg-white p-0">
<StretchNavbar
scrollContainerRef={isScrollReady ? scrollRef : undefined}
/>
<div
className="hide-scrollbar relative h-full w-full overflow-x-hidden overflow-y-scroll bg-white"
ref={scrollRef}
>
<ToolchainLanding />
</div>
</div>
);
};
export { StretchNavbarDemo };Install the following dependencies:
npm install motion lucide-react react-use-measurebun add motion lucide-react react-use-measuredeno add motion lucide-react react-use-measurepnpm add motion lucide-react react-use-measureyarn add motion lucide-react react-use-measureCopy and paste the following code into your project.
"use client";
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import Link from "next/link";
import Image from "next/image";
import {
MenuIcon,
MoveUpRight,
Search,
SunIcon,
XIcon,
} from "lucide-react";
import {
AnimatePresence,
motion,
MotionConfig,
type Variants,
useMotionValueEvent,
useReducedMotion,
useScroll,
} from "motion/react";
import useMeasure from "react-use-measure";
import { cn } from "@/lib/utils";
import useScreenSize from "@/hooks/use-screen-size";
import racLogo from "@/public/assets/rac_logo.png";
import { useHydration } from "@/hooks/use-hydration";
const SCROLL_COLLAPSE_THRESHOLD = 200;
const MOBILE_BREAKPOINT = 768;
const navBarTransition = {
tween: { type: "tween" as const, duration: 0.35, ease: [0.22, 1, 0.36, 1] as const },
spring: { duration: 0.35, type: "spring" as const, bounce: 0.2 },
menuIcon: { duration: 0.2, ease: "backOut" as const },
};
const DRAWER_LINK_STAGGER_S = 0.1;
const navbarColors = {
link: "text-[#555555] hover:text-[#101010] dark:text-[#949494] dark:hover:text-[#f5f5f5]",
icon: "text-[#101010] dark:text-[#f5f5f5]",
surfaceMuted: "bg-[#737373]/5 dark:bg-white/5",
surfaceHover: "hover:bg-[#737373]/10 dark:hover:bg-white/10",
glass: "border-[#e5e5e5]/50 bg-[#ffffff]/70 dark:border-white/10 dark:bg-[#141414]/60",
search: "bg-white dark:bg-[#1a1a1a]",
} as const;
const drawerLinkVariants: Variants = {
hidden: { opacity: 0, y: -10 },
visible: (index: number) => ({
opacity: 1,
y: 0,
transition: { type: "spring", duration: 0.5, bounce: 0, delay: index * DRAWER_LINK_STAGGER_S },
}),
exit: {
opacity: 0,
y: -10,
transition: { ...navBarTransition.spring, delay: 0 },
},
};
type NavLink = {
href: string;
label: string;
ariaLabel: string;
external?: boolean;
};
const navLinks: NavLink[] = [
{
href: "/about",
label: "About",
ariaLabel: "Go to about page",
},
{
href: "/Documentation",
label: "Documentation",
ariaLabel: "Go to documentation page",
},
{
href: "#",
label: "Github",
external: true,
ariaLabel: "Visit Github repository",
},
];
function useScrolledPast(threshold = SCROLL_COLLAPSE_THRESHOLD) {
const { scrollY } = useScroll();
const [isPassed, setIsPassed] = useState(false);
useMotionValueEvent(scrollY, "change", (latest) => {
setIsPassed(latest > threshold);
});
return isPassed;
}
function ThemeToggle({ className }: { className?: string }) {
return (
<button
type="button"
className={cn(
"p-2 rounded-md transition-colors duration-300",
navbarColors.surfaceHover,
navbarColors.icon,
className,
)}
aria-label="Toggle theme"
>
<SunIcon className="size-4" />
</button>
);
}
function NavbarLogo({ className }: { className?: string }) {
return (
<Link
href="/"
className={cn("transition-opacity hover:opacity-80", className)}
aria-label="Back to home"
>
<Image
src={racLogo}
alt="Springr Logo"
className="size-8 object-contain"
/>
</Link>
);
}
function ExternalLinkIcon() {
return (
<div className="relative overflow-hidden">
<MoveUpRight className="size-4 group-hover:transition-all group-hover:duration-300 group-hover:-translate-y-full group-hover:translate-x-full" />
<MoveUpRight className="absolute size-4 top-[160%] group-hover:top-1/2 -translate-y-1/2 left-[-60%] group-hover:left-1/2 -translate-x-1/2 group-hover:transition-all group-hover:duration-300" />
</div>
);
}
function NavLinkItem({
link,
}: {
link: NavLink;
}) {
return (
<Link
href={link.href}
target={link.external ? "_blank" : undefined}
rel={link.external ? "noopener noreferrer" : undefined}
className={cn(
"transition-colors duration-300 group flex gap-2 items-center",
navbarColors.link,
)}
aria-label={link.ariaLabel}
>
{link.label}
{link.external && <ExternalLinkIcon />}
</Link>
);
}
function NavLinksList({
variant,
className,
}: {
variant: "desktop" | "drawer";
className?: string;
}) {
const isDrawer = variant === "drawer";
return (
<ul
className={cn(
isDrawer ? "flex flex-col gap-2 mt-12" : "flex flex-row items-center gap-6",
className,
)}
aria-label="Navigation links"
>
{navLinks.map((link, index) => {
const item = <NavLinkItem link={link} />;
if (isDrawer) {
return (
<motion.li
key={link.href}
variants={drawerLinkVariants}
custom={index}
initial="hidden"
animate="visible"
exit="exit"
>
{item}
</motion.li>
);
}
return <li className="list-none" key={link.href}>{item}</li>;
})}
</ul>
);
}
function NavActions({ searchCollapsed }: { searchCollapsed: boolean }) {
return (
<div className="flex items-center gap-2">
<SearchInput isPassed={searchCollapsed} />
<ThemeToggle />
</div>
);
}
function SearchInput({
isPassed,
className,
}: {
isPassed: boolean;
className?: string;
}) {
const [ref, bounds] = useMeasure();
const [canAnimateSize, setCanAnimateSize] = useState(false);
const hasMeasuredSize = bounds.width > 0 && bounds.height > 0;
const shouldReduceMotion = useReducedMotion();
useLayoutEffect(() => {
if (hasMeasuredSize && !canAnimateSize) {
setCanAnimateSize(true);
}
}, [hasMeasuredSize, canAnimateSize]);
const shouldSnapSize = hasMeasuredSize && !canAnimateSize;
return (
<MotionConfig
transition={
shouldSnapSize || shouldReduceMotion
? { duration: 0 }
: hasMeasuredSize
? { duration: 0.25, type: "spring", bounce: 0.1 }
: undefined
}
>
<motion.button
initial={false}
animate={
hasMeasuredSize
? { width: bounds.width, height: bounds.height }
: {}
}
type="button"
className={cn(
"relative overflow-hidden border border-border rounded-md",
navbarColors.search,
className,
)}
aria-label="Open searchbar documentation"
>
<div ref={ref} className="flex relative items-center gap-2 w-fit">
<AnimatePresence mode="popLayout">
{!isPassed && (
<motion.div
key="search-expanded"
initial={false}
animate={{
filter: "blur(0px)",
clipPath: "inset(0 0 0 0)",
width: "100%",
}}
exit={{
filter: "blur(4px)",
clipPath: "inset(0 100% 0 0)",
width: 0,
}}
className="flex gap-2 items-center p-2 cursor-pointer"
>
<Search className="size-4 text-muted-foreground" />
<motion.input
type="text"
className="outline-none text-xs flex-1 w-20 bg-transparent text-foreground placeholder:text-muted-foreground"
readOnly
tabIndex={-1}
placeholder="Search..."
/>
</motion.div>
)}
</AnimatePresence>
<motion.kbd className="text-xs text-muted-foreground aspect-square px-3 py-2">
/
</motion.kbd>
</div>
</motion.button>
</MotionConfig>
);
}
function AnimatedMenuIcon({ open }: { open: boolean }) {
const rotate = open ? 45 : -45;
return (
<AnimatePresence mode="popLayout" initial={false}>
<motion.div
key={open ? "close" : "menu"}
initial={{ opacity: 0, filter: "blur(4px)", scale: 0.9, rotate }}
animate={{ opacity: 1, filter: "blur(0px)", scale: 1, rotate: 0 }}
exit={{ opacity: 0, filter: "blur(4px)", scale: 0.9, rotate }}
transition={navBarTransition.menuIcon}
>
{open ? (
<XIcon className={cn("size-4", navbarColors.icon)} />
) : (
<MenuIcon className={cn("size-4", navbarColors.icon)} />
)}
</motion.div>
</AnimatePresence>
);
}
function MobileMenu() {
const [open, setOpen] = useState(false);
const menuButtonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (!open) return;
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setOpen(false);
menuButtonRef.current?.focus();
}
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, [open]);
return (
<div className="md:hidden">
<button
ref={menuButtonRef}
type="button"
onClick={() => setOpen((prev) => !prev)}
className={cn(
"p-2 rounded-md transition-colors duration-300 z-20",
navbarColors.surfaceMuted,
navbarColors.surfaceHover,
)}
aria-expanded={open}
aria-label={open ? "Close menu" : "Open menu"}
>
<AnimatedMenuIcon open={open} />
</button>
<AnimatePresence mode="wait" initial={false}>
{open && (
<MotionConfig transition={navBarTransition.spring}>
<motion.div
key="drawer-panel"
className={cn(
"absolute -inset-x-2 inset-y-1 backdrop-blur-xl rounded-lg h-fit px-2.5 py-4 -z-1 border",
navbarColors.glass,
)}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
>
<NavLinksList variant="drawer" />
<div className="mt-4 pl-0">
<ThemeToggle />
</div>
</motion.div>
</MotionConfig>
)}
</AnimatePresence>
</div>
);
}
function NavDesktop() {
const isPassed = useScrolledPast();
const [containerRef, containerBounds] = useMeasure();
const shouldReduceMotion = useReducedMotion();
const hasContainerWidth = containerBounds.width > 0;
return (
<MotionConfig
transition={shouldReduceMotion ? { duration: 0 } : navBarTransition.tween}
>
<header className={cn("absolute inset-x-4 top-4 z-50")}>
<div ref={containerRef} className="max-w-7xl mx-auto">
<motion.div
layout
animate={
hasContainerWidth
? { width: isPassed ? 550 : containerBounds.width }
: {}
}
className={cn(
"mx-auto w-full py-3 rounded-lg border border-transparent",
isPassed &&
cn("backdrop-blur-xl dark:backdrop-blur-sm px-6 border", navbarColors.glass),
)}
>
<nav
className="flex min-w-0 items-center justify-between gap-4"
aria-label="Navigation menu"
>
<motion.div layout className={`flex min-w-0 items-center z-2 ${!isPassed ? "gap-4 md:gap-8" : "gap-2 md:gap-4"}`}>
<NavbarLogo />
<NavLinksList variant="desktop" />
</motion.div>
<NavActions searchCollapsed={isPassed} />
</nav>
</motion.div>
</div>
</header>
</MotionConfig>
);
}
function NavMobile() {
return (
<header className="fixed inset-x-4 top-2 z-50">
<nav
className="flex items-center justify-between pt-4"
aria-label="Navigation menu"
>
<NavbarLogo />
<div className="flex items-center gap-2">
<SearchInput isPassed />
<MobileMenu />
</div>
</nav>
</header>
);
}
function StretchNavbar() {
const { width } = useScreenSize();
const isMobile = width < MOBILE_BREAKPOINT;
const mounted = useHydration();
if (!mounted) return null;
if (isMobile) {
return <NavMobile />;
}
return <NavDesktop />;
}
export { StretchNavbar };The navbar is absolute positioned, with adjusted top value from the top of the page.
return (
<body>
<StretchNavbar />
{children}
</body>
);