Skip to content

Fullscreen Menu Nav

Fullscreen menu navbar: a burger opens an overlay with giant links that reveal line by line and a project photo on hover.

  • Navbars
  • Architecture
  • React only · 0 deps
  • 1 file
Full screen

Browser support · All evergreen browsers (clip-path inset animations, Intl time zones for the office clocks).

Install. One command, or copy the file.

$npx shadcn@latest add https://freedesignmd.com/r/fullscreen-menu-nav.json
  • Byte-identical to the preview
  • No Tailwind or config needed
  • SSR-safe · keyboard accessible
  • Respects reduced motion & transparency
components/navbars/fullscreen-menu-nav.tsx
1/**
2 * FullscreenMenuNav, an architecture studio header whose burger opens a
3 * fullscreen overlay. Giant stacked links reveal line by line through a
4 * clip-path mask; hovering or focusing a link wipes in a project photograph
5 * with its index number, name and year. The burger morphs into an X and the
6 * "Menu" label rolls to "Close".
7 *
8 * Props: studio, tagline, offices (name + IANA time zone, shown as live
9 * clocks), items (label, href, project { src, alt, name, place, year }),
10 * contact lines.
11 * CSS vars: --fd-fsm-paper, --fd-fsm-ink, --fd-fsm-night.
12 *
13 * Browser support: all evergreen browsers (clip-path inset).
14 * Accessibility: the button has aria-expanded/aria-controls; the overlay is a
15 * modal dialog with a focus trap, Escape closes it and returns focus. Photos
16 * are decorative (the caption carries the information as text).
17 * Reduced motion: overlay and links appear with a short fade, photos swap
18 * without wiping.
19 */
20import { useEffect, useId, useRef, useState } from "react";
21
22export interface FsmProject { src: string; alt: string; name: string; place: string; year: string }
23export interface FsmItem { label: string; href: string; project: FsmProject }
24export interface FullscreenMenuNavProps {
25 studio?: string;
26 tagline?: string;
27 offices?: { name: string; tz: string }[];
28 items?: FsmItem[];
29 contact?: string[];
30 className?: string;
31}
32
33const ITEMS: FsmItem[] = [
34 { label: "Work", href: "#work", project: { src: "/img/halcyon/courtyard-pool-900.webp", alt: "", name: "Casa dei Limoni", place: "Praiano", year: "2024" } },
35 { label: "Studio", href: "#studio", project: { src: "/img/halcyon/journals-900.webp", alt: "", name: "The drawing archive", place: "Via Roma 14", year: "1998" } },
36 { label: "Practice", href: "#practice", project: { src: "/img/halcyon/ryokan-900.webp", alt: "", name: "Hanare guest house", place: "Kyoto", year: "2021" } },
37 { label: "Journal", href: "#journal", project: { src: "/img/halcyon/suite-900.webp", alt: "", name: "Suite 9, Hotel Calce", place: "Positano", year: "2023" } },
38 { label: "Contact", href: "#contact", project: { src: "/img/halcyon/positano-sunset-900.webp", alt: "", name: "Our terrace above the bay", place: "Praiano", year: "2026" } },
39];
40
41const OFFICES = [{ name: "Praiano", tz: "Europe/Rome" }, { name: "Kyoto", tz: "Asia/Tokyo" }];
42const CONTACT = ["Via Roma 14, 84010 Praiano", "studio@morokessler.it", "+39 089 874 2210"];
43
44export function FullscreenMenuNav({
45 studio = "Moro Kessler",
46 tagline = "Architects, Praiano & Kyoto",
47 offices = OFFICES,
48 items = ITEMS,
49 contact = CONTACT,
50 className,
51}: FullscreenMenuNavProps) {
52 const uid = useId();
53 const [open, setOpen] = useState(false);
54 const [shown, setShown] = useState(false);
55 const [cur, setCur] = useState(0);
56 const [prev, setPrev] = useState<number | null>(null);
57 const [times, setTimes] = useState<string[]>(offices.map(() => "--:--"));
58 const btn = useRef<HTMLButtonElement>(null);
59 const panel = useRef<HTMLDivElement>(null);
60 const root = useRef<HTMLElement>(null);
61
62 // Live office clocks (client only).
63 useEffect(() => {
64 const fmt = offices.map((o) => new Intl.DateTimeFormat("en-GB", { hour: "2-digit", minute: "2-digit", hour12: false, timeZone: o.tz }));
65 const tick = () => setTimes(fmt.map((f) => f.format(new Date())));
66 tick();
67 const t = window.setInterval(tick, 15000);
68 return () => window.clearInterval(t);
69 }, [offices.map((o) => o.tz).join()]); // eslint-disable-line react-hooks/exhaustive-deps
70
71 // Bar gains a hairline once the page scrolls.
72 useEffect(() => {
73 const on = () => root.current?.toggleAttribute("data-scrolled", window.scrollY > 8);
74 on();
75 window.addEventListener("scroll", on, { passive: true });
76 return () => window.removeEventListener("scroll", on);
77 }, []);
78
79 // Mount/unmount with an exit animation.
80 useEffect(() => {
81 if (open) {
82 setShown(true);
83 return;
84 }
85 if (!shown) return;
86 const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
87 const t = window.setTimeout(() => setShown(false), reduce ? 0 : 520);
88 return () => window.clearTimeout(t);
89 }, [open]); // eslint-disable-line react-hooks/exhaustive-deps
90
91 useEffect(() => {
92 if (!open) return;
93 const p = panel.current;
94 const t = window.setTimeout(() => p?.querySelector<HTMLElement>(".fd-fsm-link")?.focus({ preventScroll: true }), 80);
95 const prevOverflow = document.documentElement.style.overflow;
96 document.documentElement.style.overflow = "hidden";
97 const onKey = (e: KeyboardEvent) => {
98 if (e.key === "Escape") {
99 setOpen(false);
100 btn.current?.focus();
101 }
102 if (e.key === "Tab" && p) {
103 const f = [btn.current!, ...Array.from(p.querySelectorAll<HTMLElement>("a,button"))];
104 const i = f.indexOf(document.activeElement as HTMLElement);
105 if (e.shiftKey && i <= 0) {
106 e.preventDefault();
107 f[f.length - 1].focus();
108 } else if (!e.shiftKey && i === f.length - 1) {
109 e.preventDefault();
110 f[0].focus();
111 }
112 }
113 };
114 document.addEventListener("keydown", onKey);
115 return () => {
116 window.clearTimeout(t);
117 document.documentElement.style.overflow = prevOverflow;
118 document.removeEventListener("keydown", onKey);
119 };
120 }, [open]);
121
122 const pick = (i: number) => {
123 if (i === cur) return;
124 setPrev(cur);
125 setCur(i);
126 };
127
128 const p = items[cur]?.project;
129 const panelId = `${uid}-menu`;
130
131 return (
132 <header ref={root} className={`fd-fsm${className ? " " + className : ""}`} data-open={open ? "" : undefined}>
133 <style>{css}</style>
134 <div className="fd-fsm-bar">
135 <a href="#top" className="fd-fsm-brand" aria-label={`${studio}, ${tagline}`}>
136 <svg width="26" height="26" viewBox="0 0 26 26" aria-hidden="true"><path d="M1 25V9l12-8 12 8v16" fill="none" stroke="currentColor" strokeWidth="1.6" /><path d="M8 25V14h10v11" fill="none" stroke="currentColor" strokeWidth="1.6" /></svg>
137 <span className="fd-fsm-name">{studio}</span>
138 <span className="fd-fsm-tag">{tagline}</span>
139 </a>
140 <p className="fd-fsm-clocks" aria-label="Office hours">
141 {offices.map((o, i) => (
142 <span key={o.name}><b>{o.name}</b> {times[i]}</span>
143 ))}
144 </p>
145 <button ref={btn} type="button" className="fd-fsm-btn" aria-expanded={open} aria-controls={panelId} onClick={() => setOpen((o) => !o)}>
146 <span className="fd-fsm-roll" aria-hidden="true"><span>Menu</span><span>Close</span></span>
147 <span className="fd-fsm-sr">{open ? "Close menu" : "Open menu"}</span>
148 <span className="fd-fsm-lines" aria-hidden="true"><i /><i /></span>
149 </button>
150 </div>
151
152 {shown && (
153 <div ref={panel} id={panelId} className="fd-fsm-panel" role="dialog" aria-modal="true" aria-label="Site menu" data-state={open ? "open" : "closing"}>
154 <nav className="fd-fsm-nav" aria-label="Primary">
155 <ol>
156 {items.map((it, i) => (
157 <li key={it.href} style={{ ["--i" as string]: i }}>
158 <a
159 href={it.href}
160 className="fd-fsm-link"
161 data-on={cur === i ? "" : undefined}
162 onPointerEnter={() => pick(i)}
163 onFocus={() => pick(i)}
164 onClick={() => setOpen(false)}
165 >
166 <span className="fd-fsm-idx">{String(i + 1).padStart(2, "0")}</span>
167 <span className="fd-fsm-label">{it.label}</span>
168 <span className="fd-fsm-myear">{it.project.year}</span>
169 </a>
170 </li>
171 ))}
172 </ol>
173 </nav>
174 <figure className="fd-fsm-fig" aria-hidden="true">
175 <div className="fd-fsm-frame">
176 {prev !== null && items[prev] && <img key={`p${prev}`} className="fd-fsm-img fd-fsm-under" src={items[prev].project.src} alt="" width={900} height={900} />}
177 <img key={`c${cur}`} className="fd-fsm-img fd-fsm-over" src={p.src} alt={p.alt} width={900} height={900} />
178 </div>
179 <figcaption>
180 <span className="fd-fsm-cn" key={`n${cur}`}>Nº {String(cur + 1).padStart(2, "0")}</span>
181 <span className="fd-fsm-ct" key={`t${cur}`}>{p.name}, {p.place}</span>
182 <span className="fd-fsm-cy" key={`y${cur}`}>{p.year}</span>
183 </figcaption>
184 </figure>
185 <div className="fd-fsm-foot">
186 {contact.map((c) => <span key={c}>{c}</span>)}
187 <span className="fd-fsm-live"><i aria-hidden="true" />4 buildings on site this month</span>
188 </div>
189 </div>
190 )}
191 </header>
192 );
193}
194
195const sans = `ui-sans-serif, -apple-system, BlinkMacSystemFont, "SF Pro Display", "Inter", "Segoe UI", Roboto, sans-serif`;
196const mono = `ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace`;
197
198const css = `
199.fd-fsm {
200 --fd-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
201 --fd-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
202 --fd-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
203 --fd-dur-1: 120ms; --fd-dur-2: 240ms; --fd-dur-3: 420ms; --fd-dur-4: 700ms;
204 --fd-fsm-paper: #e9e5dc;
205 --fd-fsm-ink: #171614;
206 --fd-fsm-night: #141311;
207 --fd-fsm-bone: #ebe6dc;
208 position: sticky; top: 0; z-index: 50; display: block; margin: 0; padding: 0;
209 font: 400 14px/1.4 ${sans};
210 color: var(--fd-fsm-ink); background: var(--fd-fsm-paper);
211 transition: box-shadow var(--fd-dur-2) linear;
212}
213.fd-fsm[data-scrolled] { box-shadow: 0 1px 0 rgba(23,22,20,0.16); }
214.fd-fsm, .fd-fsm *, .fd-fsm *::before, .fd-fsm *::after { box-sizing: border-box; }
215.fd-fsm a:focus-visible, .fd-fsm button:focus-visible { outline: 2px solid rgba(196,90,50,0.7); outline-offset: 3px; }
216.fd-fsm-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
217
218.fd-fsm-bar { position: relative; z-index: 3; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; height: 72px; margin: 0; padding: 0 28px; color: var(--fd-fsm-ink); transition: color var(--fd-dur-3) var(--fd-ease-in-out); }
219.fd-fsm[data-open] .fd-fsm-bar { color: var(--fd-fsm-bone); }
220.fd-fsm-brand { display: inline-flex; align-items: center; gap: 12px; justify-self: start; margin: 0; padding: 0; color: inherit; text-decoration: none; }
221.fd-fsm-brand svg { display: block; flex: none; }
222.fd-fsm-name { font: 800 1.0625rem/1 ${sans}; letter-spacing: -0.03em; text-transform: uppercase; }
223.fd-fsm-tag { font: 400 0.75rem/1 ${mono}; letter-spacing: 0; opacity: 0.66; }
224.fd-fsm-clocks { display: flex; gap: 22px; margin: 0; padding: 0; font: 400 0.75rem/1 ${mono}; letter-spacing: 0.02em; font-variant-numeric: tabular-nums; }
225.fd-fsm-clocks b { font-weight: 400; text-transform: uppercase; opacity: 0.55; margin-right: 4px; }
226.fd-fsm-btn { justify-self: end; display: inline-flex; align-items: center; gap: 14px; height: 44px; margin: 0 -8px 0 0; padding: 0 8px 0 14px; border: 0; border-radius: 0; background: none; color: inherit; cursor: pointer; appearance: none; -webkit-appearance: none; font: 600 0.8125rem/1 ${mono}; letter-spacing: 0.08em; text-transform: uppercase; }
227.fd-fsm-roll { position: relative; display: block; height: 14px; overflow: hidden; }
228.fd-fsm-roll span { display: block; height: 14px; line-height: 14px; transition: transform var(--fd-dur-3) var(--fd-ease-out); }
229.fd-fsm[data-open] .fd-fsm-roll span { transform: translateY(-100%); }
230.fd-fsm-lines { position: relative; display: block; width: 30px; height: 30px; }
231.fd-fsm-lines i { position: absolute; left: 3px; right: 3px; height: 1.5px; background: currentColor; transition: transform var(--fd-dur-3) var(--fd-spring), top var(--fd-dur-2) var(--fd-ease-out); }
232.fd-fsm-lines i:nth-child(1) { top: 11px; }
233.fd-fsm-lines i:nth-child(2) { top: 18px; transform-origin: center; }
234.fd-fsm-btn:hover .fd-fsm-lines i:nth-child(2) { transform: scaleX(0.6) translateX(-33%); }
235.fd-fsm[data-open] .fd-fsm-lines i:nth-child(1) { top: 14.5px; transform: rotate(45deg); }
236.fd-fsm[data-open] .fd-fsm-lines i:nth-child(2) { top: 14.5px; transform: rotate(-45deg); }
237
238.fd-fsm-panel { position: fixed; inset: 0; z-index: 2; display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); grid-template-rows: 1fr auto; gap: 0 56px; margin: 0; padding: 104px 28px 26px; background: var(--fd-fsm-night); color: var(--fd-fsm-bone); clip-path: inset(0 0 0 0); animation: fd-fsm-curtain var(--fd-dur-4) var(--fd-ease-out); overflow-y: auto; }
239.fd-fsm-panel[data-state="closing"] { clip-path: inset(0 0 100% 0); transition: clip-path 520ms var(--fd-ease-in-out); }
240@keyframes fd-fsm-curtain { from { clip-path: inset(0 0 100% 0); } }
241
242.fd-fsm-nav { align-self: center; margin: 0; padding: 0; }
243.fd-fsm-nav ol { margin: 0; padding: 0; list-style: none; }
244.fd-fsm-nav li { margin: 0; padding: 0; overflow: hidden; clip-path: inset(0 0 100% 0); animation: fd-fsm-line var(--fd-dur-4) var(--fd-ease-out) forwards; animation-delay: calc(180ms + var(--i) * 70ms); }
245.fd-fsm-panel[data-state="closing"] .fd-fsm-nav li { animation: none; clip-path: inset(0 0 0 0); }
246@keyframes fd-fsm-line { to { clip-path: inset(0 0 0 0); } }
247.fd-fsm-link { display: flex; align-items: flex-start; gap: 18px; margin: 0; padding: 0; color: var(--fd-fsm-bone); text-decoration: none; transform: translateY(70%); animation: fd-fsm-rise var(--fd-dur-4) var(--fd-ease-out) forwards; animation-delay: calc(180ms + var(--i) * 70ms); transition: opacity var(--fd-dur-2) linear; }
248@keyframes fd-fsm-rise { to { transform: none; } }
249.fd-fsm-nav ol:hover .fd-fsm-link, .fd-fsm-nav ol:focus-within .fd-fsm-link { opacity: 0.28; }
250.fd-fsm-nav ol .fd-fsm-link[data-on] { opacity: 1; }
251.fd-fsm-idx { flex: none; width: 2.4em; margin: 0; padding: 1.5em 0 0; font: 400 0.75rem/1 ${mono}; color: var(--fd-fsm-bone); opacity: 0.55; }
252.fd-fsm-label { display: block; font: 800 clamp(3rem, 8.6vw, 8.25rem)/0.9 ${sans}; letter-spacing: -0.055em; text-transform: uppercase; padding: 0 0 0.04em; transition: transform var(--fd-dur-3) var(--fd-ease-out); }
253.fd-fsm-link[data-on] .fd-fsm-label { transform: translateX(0.12em); }
254.fd-fsm-link:focus-visible { outline-offset: -2px; }
255.fd-fsm-myear { display: none; }
256
257.fd-fsm-fig { align-self: center; justify-self: end; width: 100%; max-width: 460px; margin: 0; padding: 0; opacity: 0; animation: fd-fsm-fade var(--fd-dur-4) var(--fd-ease-out) 380ms forwards; }
258@keyframes fd-fsm-fade { to { opacity: 1; } }
259.fd-fsm-frame { position: relative; aspect-ratio: 4 / 5; overflow: hidden; background: #26241f; }
260.fd-fsm-img { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; }
261.fd-fsm-over { animation: fd-fsm-wipe var(--fd-dur-4) var(--fd-ease-out); }
262@keyframes fd-fsm-wipe { from { clip-path: inset(100% 0 0 0); transform: scale(1.08); } to { clip-path: inset(0 0 0 0); transform: scale(1); } }
263.fd-fsm-fig figcaption { display: grid; grid-template-columns: auto 1fr auto; align-items: baseline; gap: 14px; margin: 14px 0 0; padding: 12px 0 0; border-top: 1px solid rgba(235,230,220,0.22); }
264.fd-fsm-cn, .fd-fsm-cy { font: 400 0.8125rem/1 ${mono}; color: var(--fd-fsm-bone); }
265.fd-fsm-cy { font-size: 1.5rem; letter-spacing: -0.02em; }
266.fd-fsm-ct { font: 500 0.9375rem/1.3 ${sans}; color: var(--fd-fsm-bone); opacity: 0.8; }
267.fd-fsm-cn, .fd-fsm-ct, .fd-fsm-cy { animation: fd-fsm-up var(--fd-dur-3) var(--fd-ease-out); }
268@keyframes fd-fsm-up { from { opacity: 0; transform: translateY(6px); } }
269
270.fd-fsm-foot { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 8px 32px; align-items: center; margin: 28px 0 0; padding: 18px 0 0; border-top: 1px solid rgba(235,230,220,0.18); font: 400 0.75rem/1.3 ${mono}; color: rgba(235,230,220,0.7); opacity: 0; animation: fd-fsm-fade var(--fd-dur-4) var(--fd-ease-out) 520ms forwards; }
271.fd-fsm-live { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; color: var(--fd-fsm-bone); }
272.fd-fsm-live i { display: block; width: 7px; height: 7px; border-radius: 9999px; background: #c45a32; }
273
274@media (max-width: 860px) {
275 .fd-fsm-clocks { display: none; }
276 .fd-fsm-bar { grid-template-columns: 1fr auto; height: 64px; padding: 0 18px; }
277 .fd-fsm-tag { display: none; }
278 .fd-fsm-panel { grid-template-columns: 1fr; grid-template-rows: auto 1fr auto; padding: 88px 18px 22px; gap: 0; }
279 .fd-fsm-fig { order: -1; justify-self: stretch; max-width: none; margin: 0 0 24px; }
280 .fd-fsm-frame { aspect-ratio: 16 / 9; }
281 .fd-fsm-fig figcaption { margin-top: 10px; padding-top: 8px; }
282 .fd-fsm-cy { font-size: 1rem; }
283 .fd-fsm-nav { align-self: start; }
284 .fd-fsm-label { font-size: clamp(2.75rem, 14vw, 4.5rem); }
285 .fd-fsm-myear { display: block; margin-left: auto; padding: 1.4em 0 0; font: 400 0.75rem/1 ${mono}; opacity: 0.55; }
286 .fd-fsm-idx { width: 2em; padding-top: 1.2em; }
287 .fd-fsm-live { margin-left: 0; }
288}
289@media (prefers-reduced-motion: reduce) {
290 .fd-fsm-panel { animation: fd-fsm-fade var(--fd-dur-1) linear; }
291 .fd-fsm-panel[data-state="closing"] { transition: none; }
292 .fd-fsm-nav li, .fd-fsm-link { animation: none; clip-path: none; transform: none; }
293 .fd-fsm-fig, .fd-fsm-foot { animation: none; opacity: 1; }
294 .fd-fsm-over, .fd-fsm-cn, .fd-fsm-ct, .fd-fsm-cy { animation: none; }
295 .fd-fsm-lines i, .fd-fsm-roll span, .fd-fsm-label { transition: none; }
296}
297`;
298
299/* ── Demo ─────────────────────────────────────────────────────────────── */
300
301const WORKS = [
302 { n: "014", name: "Casa dei Limoni", place: "Praiano", type: "House, restoration", year: "2024" },
303 { n: "013", name: "Suite 9, Hotel Calce", place: "Positano", type: "Interior", year: "2023" },
304 { n: "012", name: "Villa Tre Pini pool", place: "Furore", type: "Landscape", year: "2022" },
305 { n: "011", name: "Hanare guest house", place: "Kyoto", type: "Hospitality", year: "2021" },
306 { n: "010", name: "Cantina Sciacca", place: "Ravello", type: "Winery, new build", year: "2019" },
307];
308
309export function Demo() {
310 return (
311 <div className="fd-fsm-demo">
312 <style>{demoCss}</style>
313 <FullscreenMenuNav />
314 <main>
315 <section className="fd-fsm-demo-hero">
316 <p className="fd-fsm-demo-k">Since 1998 · 41 buildings · 3 of them still unfinished, on purpose</p>
317 <h1>We build slowly, in stone, close to water.</h1>
318 </section>
319 <img className="fd-fsm-demo-img" src="/img/halcyon/pool-lemons.webp" alt="Pool terrace under lemon trees, Villa Tre Pini" width={1024} height={1024} />
320 <section className="fd-fsm-demo-list" aria-label="Selected work">
321 {WORKS.map((w) => (
322 <a key={w.n} href="#work" className="fd-fsm-demo-row">
323 <span>{w.n}</span><b>{w.name}</b><span>{w.place}</span><span>{w.type}</span><span>{w.year}</span>
324 </a>
325 ))}
326 </section>
327 </main>
328 </div>
329 );
330}
331
332const demoCss = `
333.fd-fsm-demo { margin: 0; padding: 0; min-height: 100vh; background: #e9e5dc; color: #171614; font: 400 15px/1.5 ${sans}; }
334.fd-fsm-demo *, .fd-fsm-demo *::before, .fd-fsm-demo *::after { box-sizing: border-box; }
335.fd-fsm-demo main { display: block; margin: 0; padding: 0 28px 120px; }
336.fd-fsm-demo-hero { display: grid; grid-template-columns: 1fr 3fr; gap: 28px; align-items: end; margin: 0; padding: 96px 0 48px; }
337.fd-fsm-demo-k { margin: 0 0 12px; font: 400 0.75rem/1.5 ${mono}; color: #5f5b53; max-width: 26ch; }
338.fd-fsm-demo h1 { margin: 0; font: 800 clamp(2.75rem, 7.4vw, 7rem)/0.9 ${sans}; letter-spacing: -0.055em; text-transform: uppercase; color: #171614; }
339.fd-fsm-demo-img { display: block; width: 100%; height: 56vh; min-height: 320px; object-fit: cover; }
340.fd-fsm-demo-list { margin: 48px 0 0; padding: 0; border-top: 1.5px solid #171614; }
341.fd-fsm-demo-row { display: grid; grid-template-columns: 80px 2fr 1fr 1fr 80px; gap: 16px; align-items: baseline; padding: 18px 0; border-bottom: 1px solid rgba(23,22,20,0.2); color: #171614; text-decoration: none; font: 400 0.8125rem/1.3 ${mono}; transition: padding var(--fd-dur-2, 240ms) cubic-bezier(0.22, 1, 0.36, 1); }
342.fd-fsm-demo-row b { font: 700 1.5rem/1.1 ${sans}; letter-spacing: -0.03em; }
343.fd-fsm-demo-row span:last-child { text-align: right; }
344.fd-fsm-demo-row:hover { background: #e2ddd2; }
345@media (max-width: 860px) {
346 .fd-fsm-demo main { padding: 0 18px 80px; }
347 .fd-fsm-demo-hero { grid-template-columns: 1fr; padding: 48px 0 32px; }
348 .fd-fsm-demo-row { grid-template-columns: 44px 1fr 48px; }
349 .fd-fsm-demo-row span:nth-child(3), .fd-fsm-demo-row span:nth-child(4) { display: none; }
350 .fd-fsm-demo-row b { font-size: 1.125rem; }
351}
352`;

More Navbars. Same file contract.

Floating Capsule Nav, navbars preview: Sticky navbar that springs from a full-width bar into a floating capsule on scroll, with a live status light…

Floating Capsule Nav

Sticky navbar that springs from a full-width bar into a floating capsule on scroll, with a live status light and version chip.

Command Nav, navbars preview: Navbar with command palette: search opens a real ⌘K palette with recents, grouped fuzzy results and a live…

Command Nav

Navbar with command palette: search opens a real ⌘K palette with recents, grouped fuzzy results and a live preview.

App Sidebar Nav, navbars preview: App sidebar navigation for React: workspace switcher, nested groups, badges, a sliding active marker and a…

App Sidebar Nav

App sidebar navigation for React: workspace switcher, nested groups, badges, a sliding active marker and a collapsible icon rail.

Questions about Fullscreen Menu Nav

Run npx shadcn@latest add https://freedesignmd.com/r/fullscreen-menu-nav.json, or open the page and copy the file into your project. It has zero dependencies besides React. What you copy is the exact file that renders the live demo.

Fullscreen menu navbar: a burger opens an overlay with giant links that reveal line by line and a project photo on hover. It is a free, MIT-licensed React navbars component from the freedesignmd library.

Yes. It is free and MIT-licensed, use, modify and ship it in personal and commercial projects without attribution.

All evergreen browsers (clip-path inset animations, Intl time zones for the office clocks).

Yes. It is a plain React component with its own styles, so it runs in Next.js, Vite, Remix and Lovable projects, and it needs no Tailwind setup. It has zero dependencies besides React.