22 / 25
All 25 in 25 Tailwind CSS Sticky Navigations
- 01Tailwind CSS Sticky Navbar (No JavaScript, sticky top-0)
- 02Tailwind Sticky Header with Flexbox (Logo, Links, CTA)
- 03Responsive Tailwind Sticky Navbar with Hamburger Menu
- 04Tailwind Full-Width Sticky Header with Max-Width Container
- 05Tailwind Floating Glass Pill Navbar (Backdrop Blur)
- 06Tailwind Sticky Header Shrink on Scroll
- 07Tailwind Hide on Scroll Down, Show on Scroll Up Navbar
- 08Tailwind Transparent to Solid Navbar on Scroll
- 09Tailwind Sticky Header with Reading Progress Bar
- 10Tailwind Sticky Logo Shrink: Wordmark to Icon on Scroll
- 11Tailwind Navbar with Sliding Underline Active Link
- 12Tailwind Dark Mode Sticky Navbar with Theme Toggle
- 13Minimal Tailwind Sticky Header with Single CTA Button
- 14Tailwind Split Navbar with Centered Logo
- 15Neumorphic Sticky Navbar in Tailwind CSS
- 16Tailwind Sticky Header with Mega Menu Dropdown
- 17Tailwind E-Commerce Header with Search Bar and Cart Badge
- 18Tailwind Sticky Announcement Bar Above Navbar
- 19Tailwind Sticky Sub-Navigation Bar Under Main Header
- 20Tailwind Expandable Search Icon Navbar
- 21Tailwind Sticky Sidebar Table of Contents
- 22Tailwind Mobile Bottom Navigation Bar with Safe Area
- 23Tailwind Scrollspy Navbar: Highlight Active Section
- 24Tailwind Scrollable Sticky Tabs with Scroll Snap
- 25Tailwind Sticky Header with Social Share Buttons
Tailwind Mobile Bottom Navigation Bar with Safe Area
An app-style bottom tab bar pinned with sticky bottom-0 inside a phone shell, padded with pb-[max(env(safe-area-inset-bottom),1.25rem)] so it clears the iPhone home indicator on real devices and still breathes everywhere else. Four working tabs swap their panels, the active tab gets a Material-style pill, and the Inbox badge clears when you visit it.
Complete Tailwind CSS landing page templates in HTML, React, Next.js and Vue · from $49
Cruip · Affiliate link
The code
<div class="tsn-22 grid w-full min-h-svh place-items-center bg-tsn22-stage bg-[radial-gradient(60%_50%_at_50%_0%,color-mix(in_oklab,var(--color-tsn22-acc)_25%,transparent),transparent)] p-4 font-tsn22-sans text-tsn22-ink antialiased" aria-label="Mobile bottom tab bar with safe area demo">
<div class="relative h-[min(800px,calc(100svh-2rem))] w-[min(390px,100%)] overflow-hidden rounded-[3rem] border-[10px] border-[#050506] bg-tsn22-bg shadow-[0_40px_80px_-30px_rgb(0_0_0/0.8),0_0_0_1px_rgb(255_255_255/0.08)]">
<span aria-hidden="true" class="absolute left-1/2 top-2.5 z-50 h-7 w-28 -translate-x-1/2 rounded-full bg-black"></span>
<div data-tsn22-scroller class="h-full overflow-y-auto overscroll-contain [scrollbar-width:none]">
<div class="flex min-h-full flex-col">
<header class="sticky top-0 z-20 bg-tsn22-bg/80 px-5 pb-3 pt-14 backdrop-blur-xl">
<p class="font-tsn22-mono text-[10.5px] uppercase tracking-[0.16em] text-tsn22-acc">sticky bottom-0 · env(safe-area)</p>
<h1 data-tsn22-title class="mt-1 text-[30px] font-semibold tracking-[-0.03em]">Home</h1>
</header>
<main class="flex-1 px-4 pb-6">
<section data-tsn22-panel="home" class="grid gap-4">
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#ff8a5b,#ff3d77)]"></div>
<div class="p-4"><h3 class="font-semibold">Morning focus mix</h3><p class="mt-1 text-sm text-tsn22-mut">42 min · Updated today</p></div>
</article>
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#6a5cff,#2bd2ff)]"></div>
<div class="p-4"><h3 class="font-semibold">Deep work, no lyrics</h3><p class="mt-1 text-sm text-tsn22-mut">1 h 18 min · 12 tracks</p></div>
</article>
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#22c55e,#0ea5a4)]"></div>
<div class="p-4"><h3 class="font-semibold">Long run tempo</h3><p class="mt-1 text-sm text-tsn22-mut">55 min · 168 bpm</p></div>
</article>
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#f59e0b,#ef4444)]"></div>
<div class="p-4"><h3 class="font-semibold">Wind-down</h3><p class="mt-1 text-sm text-tsn22-mut">30 min · Ambient</p></div>
</article>
</section>
<section data-tsn22-panel="explore" hidden class="grid grid-cols-2 gap-3">
<a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Focus</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Running</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Sleep</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Jazz</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Lo-fi</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Classical</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Podcasts</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">New</a>
</section>
<section data-tsn22-panel="inbox" hidden class="grid gap-2">
<div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">M</span><div class="min-w-0"><p class="font-semibold">Maya</p><p class="truncate text-sm text-tsn22-mut">Shared “Deep work” with you</p></div></div><div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">L</span><div class="min-w-0"><p class="font-semibold">Leo</p><p class="truncate text-sm text-tsn22-mut">Started following you</p></div></div><div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">W</span><div class="min-w-0"><p class="font-semibold">Weekly mix</p><p class="truncate text-sm text-tsn22-mut">Your new mix is ready</p></div></div><div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">A</span><div class="min-w-0"><p class="font-semibold">Ari</p><p class="truncate text-sm text-tsn22-mut">Liked your playlist</p></div></div>
</section>
<section data-tsn22-panel="profile" hidden class="grid justify-items-center gap-3 rounded-3xl bg-tsn22-card p-8 text-center">
<span aria-hidden="true" class="size-20 rounded-full bg-[linear-gradient(135deg,var(--color-tsn22-acc),var(--color-tsn22-hot))]"></span>
<p class="text-xl font-semibold">Sam Rivera</p><p class="text-sm text-tsn22-mut">128 playlists · 2.4k followers</p>
</section>
<p class="mt-6 text-center text-[13px] leading-relaxed text-tsn22-mut">The bar below is <code class="font-tsn22-mono text-tsn22-ink">sticky bottom-0</code> inside this scroller, padded by <code class="font-tsn22-mono text-tsn22-ink">max(env(safe-area-inset-bottom), 1.25rem)</code>.</p>
</main>
<nav aria-label="Tabs" class="sticky bottom-0 z-20 border-t border-tsn22-line bg-tsn22-bar/90 px-2 pt-2 pb-[max(env(safe-area-inset-bottom),1.25rem)] backdrop-blur-xl">
<div class="flex">
<button type="button" data-tsn22-tab="home" aria-current="page" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M3 11 12 4l9 7v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z"/></svg></span>Home
</button>
<button type="button" data-tsn22-tab="explore" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg></span>Explore
</button>
<button type="button" data-tsn22-tab="inbox" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z"/></svg><span data-tsn22-badge class="absolute right-2.5 top-0.5 grid h-4 min-w-4 place-items-center rounded-full bg-tsn22-hot px-1 text-[10px] font-bold text-white ring-2 ring-tsn22-bar">3</span></span>Inbox
</button>
<button type="button" data-tsn22-tab="profile" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg></span>Profile
</button>
</div>
<span aria-hidden="true" class="absolute bottom-1.5 left-1/2 h-1 w-32 -translate-x-1/2 rounded-full bg-tsn22-ink/80"></span>
</nav>
</div>
</div>
</div>
</div><div class="tsn-22 grid w-full min-h-svh place-items-center bg-tsn22-stage bg-[radial-gradient(60%_50%_at_50%_0%,color-mix(in_oklab,var(--color-tsn22-acc)_25%,transparent),transparent)] p-4 font-tsn22-sans text-tsn22-ink antialiased" aria-label="Mobile bottom tab bar with safe area demo">
<div class="relative h-[min(800px,calc(100svh-2rem))] w-[min(390px,100%)] overflow-hidden rounded-[3rem] border-[10px] border-[#050506] bg-tsn22-bg shadow-[0_40px_80px_-30px_rgb(0_0_0/0.8),0_0_0_1px_rgb(255_255_255/0.08)]">
<span aria-hidden="true" class="absolute left-1/2 top-2.5 z-50 h-7 w-28 -translate-x-1/2 rounded-full bg-black"></span>
<div data-tsn22-scroller class="h-full overflow-y-auto overscroll-contain [scrollbar-width:none]">
<div class="flex min-h-full flex-col">
<header class="sticky top-0 z-20 bg-tsn22-bg/80 px-5 pb-3 pt-14 backdrop-blur-xl">
<p class="font-tsn22-mono text-[10.5px] uppercase tracking-[0.16em] text-tsn22-acc">sticky bottom-0 · env(safe-area)</p>
<h1 data-tsn22-title class="mt-1 text-[30px] font-semibold tracking-[-0.03em]">Home</h1>
</header>
<main class="flex-1 px-4 pb-6">
<section data-tsn22-panel="home" class="grid gap-4">
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#ff8a5b,#ff3d77)]"></div>
<div class="p-4"><h3 class="font-semibold">Morning focus mix</h3><p class="mt-1 text-sm text-tsn22-mut">42 min · Updated today</p></div>
</article>
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#6a5cff,#2bd2ff)]"></div>
<div class="p-4"><h3 class="font-semibold">Deep work, no lyrics</h3><p class="mt-1 text-sm text-tsn22-mut">1 h 18 min · 12 tracks</p></div>
</article>
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#22c55e,#0ea5a4)]"></div>
<div class="p-4"><h3 class="font-semibold">Long run tempo</h3><p class="mt-1 text-sm text-tsn22-mut">55 min · 168 bpm</p></div>
</article>
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#f59e0b,#ef4444)]"></div>
<div class="p-4"><h3 class="font-semibold">Wind-down</h3><p class="mt-1 text-sm text-tsn22-mut">30 min · Ambient</p></div>
</article>
</section>
<section data-tsn22-panel="explore" hidden class="grid grid-cols-2 gap-3">
<a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Focus</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Running</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Sleep</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Jazz</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Lo-fi</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Classical</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Podcasts</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">New</a>
</section>
<section data-tsn22-panel="inbox" hidden class="grid gap-2">
<div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">M</span><div class="min-w-0"><p class="font-semibold">Maya</p><p class="truncate text-sm text-tsn22-mut">Shared “Deep work” with you</p></div></div><div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">L</span><div class="min-w-0"><p class="font-semibold">Leo</p><p class="truncate text-sm text-tsn22-mut">Started following you</p></div></div><div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">W</span><div class="min-w-0"><p class="font-semibold">Weekly mix</p><p class="truncate text-sm text-tsn22-mut">Your new mix is ready</p></div></div><div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">A</span><div class="min-w-0"><p class="font-semibold">Ari</p><p class="truncate text-sm text-tsn22-mut">Liked your playlist</p></div></div>
</section>
<section data-tsn22-panel="profile" hidden class="grid justify-items-center gap-3 rounded-3xl bg-tsn22-card p-8 text-center">
<span aria-hidden="true" class="size-20 rounded-full bg-[linear-gradient(135deg,var(--color-tsn22-acc),var(--color-tsn22-hot))]"></span>
<p class="text-xl font-semibold">Sam Rivera</p><p class="text-sm text-tsn22-mut">128 playlists · 2.4k followers</p>
</section>
<p class="mt-6 text-center text-[13px] leading-relaxed text-tsn22-mut">The bar below is <code class="font-tsn22-mono text-tsn22-ink">sticky bottom-0</code> inside this scroller, padded by <code class="font-tsn22-mono text-tsn22-ink">max(env(safe-area-inset-bottom), 1.25rem)</code>.</p>
</main>
<nav aria-label="Tabs" class="sticky bottom-0 z-20 border-t border-tsn22-line bg-tsn22-bar/90 px-2 pt-2 pb-[max(env(safe-area-inset-bottom),1.25rem)] backdrop-blur-xl">
<div class="flex">
<button type="button" data-tsn22-tab="home" aria-current="page" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M3 11 12 4l9 7v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z"/></svg></span>Home
</button>
<button type="button" data-tsn22-tab="explore" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg></span>Explore
</button>
<button type="button" data-tsn22-tab="inbox" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z"/></svg><span data-tsn22-badge class="absolute right-2.5 top-0.5 grid h-4 min-w-4 place-items-center rounded-full bg-tsn22-hot px-1 text-[10px] font-bold text-white ring-2 ring-tsn22-bar">3</span></span>Inbox
</button>
<button type="button" data-tsn22-tab="profile" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg></span>Profile
</button>
</div>
<span aria-hidden="true" class="absolute bottom-1.5 left-1/2 h-1 w-32 -translate-x-1/2 rounded-full bg-tsn22-ink/80"></span>
</nav>
</div>
</div>
</div>
</div>@import "tailwindcss";
@theme {
--color-tsn22-stage: #08080a;
--color-tsn22-stage: oklch(0.13 0.004 285);
--color-tsn22-bg: #0f0f12;
--color-tsn22-bg: oklch(0.17 0.006 285);
--color-tsn22-bar: #16161b;
--color-tsn22-bar: oklch(0.2 0.008 285);
--color-tsn22-card: #1c1c22;
--color-tsn22-card: oklch(0.23 0.01 285);
--color-tsn22-ink: #f4f4f6;
--color-tsn22-ink: oklch(0.96 0.004 285);
--color-tsn22-mut: #a3a3ad;
--color-tsn22-mut: oklch(0.73 0.01 285);
--color-tsn22-acc: #a3e635;
--color-tsn22-acc: oklch(0.86 0.2 128);
--color-tsn22-hot: #f43f5e;
--color-tsn22-hot: oklch(0.64 0.22 15);
--color-tsn22-line: #f4f4f61a;
--color-tsn22-line: oklch(0.96 0.004 285 / 0.1);
--font-tsn22-sans: 'Outfit', system-ui, sans-serif;
--font-tsn22-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, restated in CSS so the page is sized before the browser build compiles. */
.tsn-22 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tsn22-stage: #08080a;
--color-tsn22-stage: oklch(0.13 0.004 285);
--color-tsn22-bg: #0f0f12;
--color-tsn22-bg: oklch(0.17 0.006 285);
--color-tsn22-bar: #16161b;
--color-tsn22-bar: oklch(0.2 0.008 285);
--color-tsn22-card: #1c1c22;
--color-tsn22-card: oklch(0.23 0.01 285);
--color-tsn22-ink: #f4f4f6;
--color-tsn22-ink: oklch(0.96 0.004 285);
--color-tsn22-mut: #a3a3ad;
--color-tsn22-mut: oklch(0.73 0.01 285);
--color-tsn22-acc: #a3e635;
--color-tsn22-acc: oklch(0.86 0.2 128);
--color-tsn22-hot: #f43f5e;
--color-tsn22-hot: oklch(0.64 0.22 15);
--color-tsn22-line: #f4f4f61a;
--color-tsn22-line: oklch(0.96 0.004 285 / 0.1);
--font-tsn22-sans: 'Outfit', system-ui, sans-serif;
--font-tsn22-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, restated in CSS so the page is sized before the browser build compiles. */
.tsn-22 {
width: 100%;
min-height: 100svh;
}(() => {
document.querySelectorAll('.tsn-22').forEach((root) => {
if (root.dataset.wired) return;
root.dataset.wired = '1';
const tabs = [...root.querySelectorAll('[data-tsn22-tab]')];
const panels = [...root.querySelectorAll('[data-tsn22-panel]')];
const title = root.querySelector('[data-tsn22-title]');
const scroller = root.querySelector('[data-tsn22-scroller]');
tabs.forEach((t) => t.addEventListener('click', () => {
const id = t.dataset.tsn22Tab;
tabs.forEach((x) => (x === t ? x.setAttribute('aria-current', 'page') : x.removeAttribute('aria-current')));
panels.forEach((p) => { p.hidden = p.dataset.tsn22Panel !== id; });
title.textContent = t.textContent.trim();
scroller.scrollTo({ top: 0 });
if (id === 'inbox') { const b = root.querySelector('[data-tsn22-badge]'); if (b) b.remove(); }
}));
root.querySelectorAll('[data-tsn22-panel] a[href="#"]').forEach((a) => a.addEventListener('click', (e) => e.preventDefault()));
});
})();(() => {
document.querySelectorAll('.tsn-22').forEach((root) => {
if (root.dataset.wired) return;
root.dataset.wired = '1';
const tabs = [...root.querySelectorAll('[data-tsn22-tab]')];
const panels = [...root.querySelectorAll('[data-tsn22-panel]')];
const title = root.querySelector('[data-tsn22-title]');
const scroller = root.querySelector('[data-tsn22-scroller]');
tabs.forEach((t) => t.addEventListener('click', () => {
const id = t.dataset.tsn22Tab;
tabs.forEach((x) => (x === t ? x.setAttribute('aria-current', 'page') : x.removeAttribute('aria-current')));
panels.forEach((p) => { p.hidden = p.dataset.tsn22Panel !== id; });
title.textContent = t.textContent.trim();
scroller.scrollTo({ top: 0 });
if (id === 'inbox') { const b = root.querySelector('[data-tsn22-badge]'); if (b) b.remove(); }
}));
root.querySelectorAll('[data-tsn22-panel] a[href="#"]').forEach((a) => a.addEventListener('click', (e) => e.preventDefault()));
});
})();Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tsn22-stage: #08080a;
--color-tsn22-stage: oklch(0.13 0.004 285);
--color-tsn22-bg: #0f0f12;
--color-tsn22-bg: oklch(0.17 0.006 285);
--color-tsn22-bar: #16161b;
--color-tsn22-bar: oklch(0.2 0.008 285);
--color-tsn22-card: #1c1c22;
--color-tsn22-card: oklch(0.23 0.01 285);
--color-tsn22-ink: #f4f4f6;
--color-tsn22-ink: oklch(0.96 0.004 285);
--color-tsn22-mut: #a3a3ad;
--color-tsn22-mut: oklch(0.73 0.01 285);
--color-tsn22-acc: #a3e635;
--color-tsn22-acc: oklch(0.86 0.2 128);
--color-tsn22-hot: #f43f5e;
--color-tsn22-hot: oklch(0.64 0.22 15);
--color-tsn22-line: #f4f4f61a;
--color-tsn22-line: oklch(0.96 0.004 285 / 0.1);
--font-tsn22-sans: 'Outfit', system-ui, sans-serif;
--font-tsn22-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, restated in CSS so the page is sized before the browser build compiles. */
.tsn-22 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tsn22-stage: #08080a;
--color-tsn22-stage: oklch(0.13 0.004 285);
--color-tsn22-bg: #0f0f12;
--color-tsn22-bg: oklch(0.17 0.006 285);
--color-tsn22-bar: #16161b;
--color-tsn22-bar: oklch(0.2 0.008 285);
--color-tsn22-card: #1c1c22;
--color-tsn22-card: oklch(0.23 0.01 285);
--color-tsn22-ink: #f4f4f6;
--color-tsn22-ink: oklch(0.96 0.004 285);
--color-tsn22-mut: #a3a3ad;
--color-tsn22-mut: oklch(0.73 0.01 285);
--color-tsn22-acc: #a3e635;
--color-tsn22-acc: oklch(0.86 0.2 128);
--color-tsn22-hot: #f43f5e;
--color-tsn22-hot: oklch(0.64 0.22 15);
--color-tsn22-line: #f4f4f61a;
--color-tsn22-line: oklch(0.96 0.004 285 / 0.1);
--font-tsn22-sans: 'Outfit', system-ui, sans-serif;
--font-tsn22-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, restated in CSS so the page is sized before the browser build compiles. */
.tsn-22 {
width: 100%;
min-height: 100svh;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The original's
padding-bottom:max(env(safe-area-inset-bottom),20px)maps directly onto an arbitrarypb-[max(…)]utility;.is-activebecamearia-current="page"read throughgroup-aria-[current=page]/t:. - UI upgrade: tabs now actually switch panels (Home, Explore, Inbox, Profile), update the large title, reset scroll, and clear the Inbox badge on visit.
- UI upgrade: a Material-style active pill behind the icon, a Dynamic-Island cutout and home indicator, and
overscroll-containso the shell's scroll never drags the page.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Mobile Bottom Navigation Bar with Safe Area
Source: https://codefronts.com/navigation/tailwind-css-sticky-navigations/tailwind-mobile-bottom-navigation-bar-safe-area/
An app-style bottom tab bar pinned with sticky bottom-0 inside a phone shell, padded with pb-[max(env(safe-area-inset-bottom),1.25rem)] so it clears the iPhone home indicator on real devices and still breathes everywhere else. Four working tabs swap their panels, the active tab gets a Material-style pill, and the Inbox badge clears when you visit it.
## HTML
```html
<div class="tsn-22 grid w-full min-h-svh place-items-center bg-tsn22-stage bg-[radial-gradient(60%_50%_at_50%_0%,color-mix(in_oklab,var(--color-tsn22-acc)_25%,transparent),transparent)] p-4 font-tsn22-sans text-tsn22-ink antialiased" aria-label="Mobile bottom tab bar with safe area demo">
<div class="relative h-[min(800px,calc(100svh-2rem))] w-[min(390px,100%)] overflow-hidden rounded-[3rem] border-[10px] border-[#050506] bg-tsn22-bg shadow-[0_40px_80px_-30px_rgb(0_0_0/0.8),0_0_0_1px_rgb(255_255_255/0.08)]">
<span aria-hidden="true" class="absolute left-1/2 top-2.5 z-50 h-7 w-28 -translate-x-1/2 rounded-full bg-black"></span>
<div data-tsn22-scroller class="h-full overflow-y-auto overscroll-contain [scrollbar-width:none]">
<div class="flex min-h-full flex-col">
<header class="sticky top-0 z-20 bg-tsn22-bg/80 px-5 pb-3 pt-14 backdrop-blur-xl">
<p class="font-tsn22-mono text-[10.5px] uppercase tracking-[0.16em] text-tsn22-acc">sticky bottom-0 · env(safe-area)</p>
<h1 data-tsn22-title class="mt-1 text-[30px] font-semibold tracking-[-0.03em]">Home</h1>
</header>
<main class="flex-1 px-4 pb-6">
<section data-tsn22-panel="home" class="grid gap-4">
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#ff8a5b,#ff3d77)]"></div>
<div class="p-4"><h3 class="font-semibold">Morning focus mix</h3><p class="mt-1 text-sm text-tsn22-mut">42 min · Updated today</p></div>
</article>
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#6a5cff,#2bd2ff)]"></div>
<div class="p-4"><h3 class="font-semibold">Deep work, no lyrics</h3><p class="mt-1 text-sm text-tsn22-mut">1 h 18 min · 12 tracks</p></div>
</article>
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#22c55e,#0ea5a4)]"></div>
<div class="p-4"><h3 class="font-semibold">Long run tempo</h3><p class="mt-1 text-sm text-tsn22-mut">55 min · 168 bpm</p></div>
</article>
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#f59e0b,#ef4444)]"></div>
<div class="p-4"><h3 class="font-semibold">Wind-down</h3><p class="mt-1 text-sm text-tsn22-mut">30 min · Ambient</p></div>
</article>
</section>
<section data-tsn22-panel="explore" hidden class="grid grid-cols-2 gap-3">
<a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Focus</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Running</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Sleep</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Jazz</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Lo-fi</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Classical</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Podcasts</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">New</a>
</section>
<section data-tsn22-panel="inbox" hidden class="grid gap-2">
<div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">M</span><div class="min-w-0"><p class="font-semibold">Maya</p><p class="truncate text-sm text-tsn22-mut">Shared “Deep work” with you</p></div></div><div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">L</span><div class="min-w-0"><p class="font-semibold">Leo</p><p class="truncate text-sm text-tsn22-mut">Started following you</p></div></div><div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">W</span><div class="min-w-0"><p class="font-semibold">Weekly mix</p><p class="truncate text-sm text-tsn22-mut">Your new mix is ready</p></div></div><div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">A</span><div class="min-w-0"><p class="font-semibold">Ari</p><p class="truncate text-sm text-tsn22-mut">Liked your playlist</p></div></div>
</section>
<section data-tsn22-panel="profile" hidden class="grid justify-items-center gap-3 rounded-3xl bg-tsn22-card p-8 text-center">
<span aria-hidden="true" class="size-20 rounded-full bg-[linear-gradient(135deg,var(--color-tsn22-acc),var(--color-tsn22-hot))]"></span>
<p class="text-xl font-semibold">Sam Rivera</p><p class="text-sm text-tsn22-mut">128 playlists · 2.4k followers</p>
</section>
<p class="mt-6 text-center text-[13px] leading-relaxed text-tsn22-mut">The bar below is <code class="font-tsn22-mono text-tsn22-ink">sticky bottom-0</code> inside this scroller, padded by <code class="font-tsn22-mono text-tsn22-ink">max(env(safe-area-inset-bottom), 1.25rem)</code>.</p>
</main>
<nav aria-label="Tabs" class="sticky bottom-0 z-20 border-t border-tsn22-line bg-tsn22-bar/90 px-2 pt-2 pb-[max(env(safe-area-inset-bottom),1.25rem)] backdrop-blur-xl">
<div class="flex">
<button type="button" data-tsn22-tab="home" aria-current="page" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M3 11 12 4l9 7v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z"/></svg></span>Home
</button>
<button type="button" data-tsn22-tab="explore" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg></span>Explore
</button>
<button type="button" data-tsn22-tab="inbox" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z"/></svg><span data-tsn22-badge class="absolute right-2.5 top-0.5 grid h-4 min-w-4 place-items-center rounded-full bg-tsn22-hot px-1 text-[10px] font-bold text-white ring-2 ring-tsn22-bar">3</span></span>Inbox
</button>
<button type="button" data-tsn22-tab="profile" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg></span>Profile
</button>
</div>
<span aria-hidden="true" class="absolute bottom-1.5 left-1/2 h-1 w-32 -translate-x-1/2 rounded-full bg-tsn22-ink/80"></span>
</nav>
</div>
</div>
</div>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsn22-stage: #08080a;
--color-tsn22-stage: oklch(0.13 0.004 285);
--color-tsn22-bg: #0f0f12;
--color-tsn22-bg: oklch(0.17 0.006 285);
--color-tsn22-bar: #16161b;
--color-tsn22-bar: oklch(0.2 0.008 285);
--color-tsn22-card: #1c1c22;
--color-tsn22-card: oklch(0.23 0.01 285);
--color-tsn22-ink: #f4f4f6;
--color-tsn22-ink: oklch(0.96 0.004 285);
--color-tsn22-mut: #a3a3ad;
--color-tsn22-mut: oklch(0.73 0.01 285);
--color-tsn22-acc: #a3e635;
--color-tsn22-acc: oklch(0.86 0.2 128);
--color-tsn22-hot: #f43f5e;
--color-tsn22-hot: oklch(0.64 0.22 15);
--color-tsn22-line: #f4f4f61a;
--color-tsn22-line: oklch(0.96 0.004 285 / 0.1);
--font-tsn22-sans: 'Outfit', system-ui, sans-serif;
--font-tsn22-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, restated in CSS so the page is sized before the browser build compiles. */
.tsn-22 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
document.querySelectorAll('.tsn-22').forEach((root) => {
if (root.dataset.wired) return;
root.dataset.wired = '1';
const tabs = [...root.querySelectorAll('[data-tsn22-tab]')];
const panels = [...root.querySelectorAll('[data-tsn22-panel]')];
const title = root.querySelector('[data-tsn22-title]');
const scroller = root.querySelector('[data-tsn22-scroller]');
tabs.forEach((t) => t.addEventListener('click', () => {
const id = t.dataset.tsn22Tab;
tabs.forEach((x) => (x === t ? x.setAttribute('aria-current', 'page') : x.removeAttribute('aria-current')));
panels.forEach((p) => { p.hidden = p.dataset.tsn22Panel !== id; });
title.textContent = t.textContent.trim();
scroller.scrollTo({ top: 0 });
if (id === 'inbox') { const b = root.querySelector('[data-tsn22-badge]'); if (b) b.remove(); }
}));
root.querySelectorAll('[data-tsn22-panel] a[href="#"]').forEach((a) => a.addEventListener('click', (e) => e.preventDefault()));
});
})();
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Mobile Bottom Navigation Bar with Safe Area
Source: https://codefronts.com/navigation/tailwind-css-sticky-navigations/tailwind-mobile-bottom-navigation-bar-safe-area/
An app-style bottom tab bar pinned with sticky bottom-0 inside a phone shell, padded with pb-[max(env(safe-area-inset-bottom),1.25rem)] so it clears the iPhone home indicator on real devices and still breathes everywhere else. Four working tabs swap their panels, the active tab gets a Material-style pill, and the Inbox badge clears when you visit it.
## HTML
```html
<div class="tsn-22 grid w-full min-h-svh place-items-center bg-tsn22-stage bg-[radial-gradient(60%_50%_at_50%_0%,color-mix(in_oklab,var(--color-tsn22-acc)_25%,transparent),transparent)] p-4 font-tsn22-sans text-tsn22-ink antialiased" aria-label="Mobile bottom tab bar with safe area demo">
<div class="relative h-[min(800px,calc(100svh-2rem))] w-[min(390px,100%)] overflow-hidden rounded-[3rem] border-[10px] border-[#050506] bg-tsn22-bg shadow-[0_40px_80px_-30px_rgb(0_0_0/0.8),0_0_0_1px_rgb(255_255_255/0.08)]">
<span aria-hidden="true" class="absolute left-1/2 top-2.5 z-50 h-7 w-28 -translate-x-1/2 rounded-full bg-black"></span>
<div data-tsn22-scroller class="h-full overflow-y-auto overscroll-contain [scrollbar-width:none]">
<div class="flex min-h-full flex-col">
<header class="sticky top-0 z-20 bg-tsn22-bg/80 px-5 pb-3 pt-14 backdrop-blur-xl">
<p class="font-tsn22-mono text-[10.5px] uppercase tracking-[0.16em] text-tsn22-acc">sticky bottom-0 · env(safe-area)</p>
<h1 data-tsn22-title class="mt-1 text-[30px] font-semibold tracking-[-0.03em]">Home</h1>
</header>
<main class="flex-1 px-4 pb-6">
<section data-tsn22-panel="home" class="grid gap-4">
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#ff8a5b,#ff3d77)]"></div>
<div class="p-4"><h3 class="font-semibold">Morning focus mix</h3><p class="mt-1 text-sm text-tsn22-mut">42 min · Updated today</p></div>
</article>
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#6a5cff,#2bd2ff)]"></div>
<div class="p-4"><h3 class="font-semibold">Deep work, no lyrics</h3><p class="mt-1 text-sm text-tsn22-mut">1 h 18 min · 12 tracks</p></div>
</article>
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#22c55e,#0ea5a4)]"></div>
<div class="p-4"><h3 class="font-semibold">Long run tempo</h3><p class="mt-1 text-sm text-tsn22-mut">55 min · 168 bpm</p></div>
</article>
<article class="overflow-hidden rounded-3xl bg-tsn22-card">
<div aria-hidden="true" class="aspect-[16/10] bg-[linear-gradient(135deg,#f59e0b,#ef4444)]"></div>
<div class="p-4"><h3 class="font-semibold">Wind-down</h3><p class="mt-1 text-sm text-tsn22-mut">30 min · Ambient</p></div>
</article>
</section>
<section data-tsn22-panel="explore" hidden class="grid grid-cols-2 gap-3">
<a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Focus</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Running</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Sleep</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Jazz</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Lo-fi</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Classical</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">Podcasts</a><a href="#" class="grid aspect-square content-end rounded-3xl bg-tsn22-card p-4 text-lg font-semibold">New</a>
</section>
<section data-tsn22-panel="inbox" hidden class="grid gap-2">
<div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">M</span><div class="min-w-0"><p class="font-semibold">Maya</p><p class="truncate text-sm text-tsn22-mut">Shared “Deep work” with you</p></div></div><div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">L</span><div class="min-w-0"><p class="font-semibold">Leo</p><p class="truncate text-sm text-tsn22-mut">Started following you</p></div></div><div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">W</span><div class="min-w-0"><p class="font-semibold">Weekly mix</p><p class="truncate text-sm text-tsn22-mut">Your new mix is ready</p></div></div><div class="flex items-center gap-3 rounded-2xl bg-tsn22-card p-3"><span aria-hidden="true" class="grid size-11 flex-none place-items-center rounded-full bg-tsn22-acc/20 font-semibold text-tsn22-acc">A</span><div class="min-w-0"><p class="font-semibold">Ari</p><p class="truncate text-sm text-tsn22-mut">Liked your playlist</p></div></div>
</section>
<section data-tsn22-panel="profile" hidden class="grid justify-items-center gap-3 rounded-3xl bg-tsn22-card p-8 text-center">
<span aria-hidden="true" class="size-20 rounded-full bg-[linear-gradient(135deg,var(--color-tsn22-acc),var(--color-tsn22-hot))]"></span>
<p class="text-xl font-semibold">Sam Rivera</p><p class="text-sm text-tsn22-mut">128 playlists · 2.4k followers</p>
</section>
<p class="mt-6 text-center text-[13px] leading-relaxed text-tsn22-mut">The bar below is <code class="font-tsn22-mono text-tsn22-ink">sticky bottom-0</code> inside this scroller, padded by <code class="font-tsn22-mono text-tsn22-ink">max(env(safe-area-inset-bottom), 1.25rem)</code>.</p>
</main>
<nav aria-label="Tabs" class="sticky bottom-0 z-20 border-t border-tsn22-line bg-tsn22-bar/90 px-2 pt-2 pb-[max(env(safe-area-inset-bottom),1.25rem)] backdrop-blur-xl">
<div class="flex">
<button type="button" data-tsn22-tab="home" aria-current="page" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M3 11 12 4l9 7v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z"/></svg></span>Home
</button>
<button type="button" data-tsn22-tab="explore" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg></span>Explore
</button>
<button type="button" data-tsn22-tab="inbox" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z"/></svg><span data-tsn22-badge class="absolute right-2.5 top-0.5 grid h-4 min-w-4 place-items-center rounded-full bg-tsn22-hot px-1 text-[10px] font-bold text-white ring-2 ring-tsn22-bar">3</span></span>Inbox
</button>
<button type="button" data-tsn22-tab="profile" class="group/t relative grid min-h-14 flex-1 place-items-center content-center gap-1 rounded-2xl text-[11px] font-semibold text-tsn22-mut transition-colors hover:text-tsn22-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsn22-acc aria-[current=page]:text-tsn22-ink">
<span class="relative grid h-8 w-14 place-items-center rounded-full transition-colors group-aria-[current=page]/t:bg-tsn22-acc/20 group-aria-[current=page]/t:text-tsn22-acc"><svg class="size-[22px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg></span>Profile
</button>
</div>
<span aria-hidden="true" class="absolute bottom-1.5 left-1/2 h-1 w-32 -translate-x-1/2 rounded-full bg-tsn22-ink/80"></span>
</nav>
</div>
</div>
</div>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsn22-stage: #08080a;
--color-tsn22-stage: oklch(0.13 0.004 285);
--color-tsn22-bg: #0f0f12;
--color-tsn22-bg: oklch(0.17 0.006 285);
--color-tsn22-bar: #16161b;
--color-tsn22-bar: oklch(0.2 0.008 285);
--color-tsn22-card: #1c1c22;
--color-tsn22-card: oklch(0.23 0.01 285);
--color-tsn22-ink: #f4f4f6;
--color-tsn22-ink: oklch(0.96 0.004 285);
--color-tsn22-mut: #a3a3ad;
--color-tsn22-mut: oklch(0.73 0.01 285);
--color-tsn22-acc: #a3e635;
--color-tsn22-acc: oklch(0.86 0.2 128);
--color-tsn22-hot: #f43f5e;
--color-tsn22-hot: oklch(0.64 0.22 15);
--color-tsn22-line: #f4f4f61a;
--color-tsn22-line: oklch(0.96 0.004 285 / 0.1);
--font-tsn22-sans: 'Outfit', system-ui, sans-serif;
--font-tsn22-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, restated in CSS so the page is sized before the browser build compiles. */
.tsn-22 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
document.querySelectorAll('.tsn-22').forEach((root) => {
if (root.dataset.wired) return;
root.dataset.wired = '1';
const tabs = [...root.querySelectorAll('[data-tsn22-tab]')];
const panels = [...root.querySelectorAll('[data-tsn22-panel]')];
const title = root.querySelector('[data-tsn22-title]');
const scroller = root.querySelector('[data-tsn22-scroller]');
tabs.forEach((t) => t.addEventListener('click', () => {
const id = t.dataset.tsn22Tab;
tabs.forEach((x) => (x === t ? x.setAttribute('aria-current', 'page') : x.removeAttribute('aria-current')));
panels.forEach((p) => { p.hidden = p.dataset.tsn22Panel !== id; });
title.textContent = t.textContent.trim();
scroller.scrollTo({ top: 0 });
if (id === 'inbox') { const b = root.querySelector('[data-tsn22-badge]'); if (b) b.remove(); }
}));
root.querySelectorAll('[data-tsn22-panel] a[href="#"]').forEach((a) => a.addEventListener('click', (e) => e.preventDefault()));
});
})();
```How this works
Bottom sticky needs a full-height column. If the content is shorter than the screen, a sticky bottom-0 bar simply sits after it. Wrapping the screen in min-h-full flex flex-col with a flex-1 main keeps the bar on the floor:
<nav class="sticky bottom-0 pb-[max(env(safe-area-inset-bottom),1.25rem)]">env(safe-area-inset-bottom) is 34px on Face-ID iPhones (with viewport-fit=cover) and 0 elsewhere; max() guarantees at least 1.25rem. Tab buttons carry aria-current="page"; the script swaps panels, scrolls the shell to top and clears the badge.
Make it yours
- Outside a device shell: put the nav as the last child of
<body>withfixed inset-x-0 bottom-0, and addpb-24to the page. - Labels only for active: add
max-[360px]:sr-only group-aria-[current=page]/t:not-sr-onlyto labels for tiny screens. - Five tabs: the buttons are
flex-1, so a fifth simply shares the width.
Gotchas — read before shipping
env(safe-area-inset-*)is 0 unless the page sets<meta name="viewport" content="…, viewport-fit=cover">.- Use
svh/dvh, notvh, for full-height app shells — mobile browser chrome makes100vhtoo tall. - Bottom bars are a mobile pattern; on desktop, convert them to a side rail.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
env() safe-area insets: Safari 11.1+, Chrome 69+, Firefox 65+. The floor is Tailwind v4's generated sheet.