14 / 30
All 30 in 30 Tailwind CSS Sidebar Layouts
- 01Tailwind SaaS Dashboard Sidebar with Native Dark Mode Switch
- 02Tailwind Collapsible Admin Sidebar with Icon Tooltips
- 03Tailwind Documentation Sidebar with Multi-Level :target Tree
- 04Tailwind Responsive Sidebar to Bottom Navigation Switch Layout
- 05Tailwind Analytics Sidebar with CSS Subgrid Column Alignment
- 06Tailwind Animated Collapsible Sidebar with Icon-Only Mode
- 07Tailwind Sticky Sidebar Layout with Scroll-Driven Progress Bar
- 08Tailwind Pure CSS Collapsible Sidebar Drawer without JavaScript
- 09Tailwind Fixed Sidebar Layout with Independently Scrolling Content
- 10Tailwind Off-Canvas Navigation Drawer with the Popover API
- 11Tailwind Right-Aligned Sidebar Layout for Inspector Panels
- 12Tailwind Two-Pane Webmail Layout with List and Reading Pane
- 13Tailwind Dual Sidebar App Shell with Left Nav and Context Panel
- 14Tailwind Mini Icon Rail Sidebar with Anchor-Positioned Active Pill
- 15Tailwind E-Commerce Filter Sidebar with Native Details Accordion
- 16Tailwind Floating Glassmorphism Sidebar with Gradient Hairline
- 17Tailwind Sidebar Menu with Pinned User Profile Footer
- 18Tailwind Push-Content Sidebar Drawer with GPU Transitions
- 19Tailwind Off-Canvas Cart Overlay with Blurred Backdrop Scrim
- 20Tailwind Sidebar with Persistent Collapsed State in localStorage
- 21Tailwind Sidebar with Sticky Search Bar Inside the Rail
- 22Tailwind Section-Grouped Sidebar Navigation with Header Labels
- 23Tailwind Sliding Active Indicator Sidebar with CSS :target
- 24Tailwind Multi-Level Nested Tree Sidebar with Details Branches
- 25Tailwind Tabbed Sidebar with Radio-Driven Panel Switching
- 26Tailwind Onboarding Wizard Sidebar with Step Progress Tracking
- 27Tailwind Resizable Sidebar Layout Using the CSS Resize Property
- 28Tailwind Three-Pane Workspace Shell with Icon Rail and List
- 29Tailwind Adaptive Sidebar Layout Using CSS Container Queries
- 30Tailwind Frosted Glass Floating Sidebar with Animated Aurora
Tailwind Mini Icon Rail Sidebar with Anchor-Positioned Active Pill
The 68px icon rail perfected: 44px targets, hover tooltips, notification dots, a bottom-pinned avatar — and an active pill that glides between icons using CSS anchor positioning. The effect teams reach for a motion library to get, with no script and a clean fallback.
Mosaic by Cruip: a complete Tailwind admin dashboard template in HTML, React, Next.js and Vue · $69
Cruip · Mosaic by Cruip · Affiliate link
The code
<section class="tsl-14 w-full h-svh min-h-svh grid grid-cols-[68px_1fr] font-tsl14-sans bg-tsl14-bg text-tsl14-ink">
<aside aria-label="Primary" class="relative flex flex-col items-center gap-1.5 py-3.5 bg-tsl14-panel border-r border-tsl14-line">
<span aria-hidden="true" class="w-[30px] h-[30px] mb-3 rounded-[9px] bg-[conic-gradient(from_45deg,var(--color-tsl14-accent),oklch(0.62_0.17_285))]"></span>
<span aria-hidden="true" class="tsl-14-pill absolute left-2 w-[3.5px] h-[26px] rounded"></span>
<nav class="flex flex-col gap-1.5">
<label data-tooltip="Home" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" checked class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M3 10.5 12 3l9 7.5M5.5 9.5V21h13V9.5"/></svg><span class="sr-only">Home</span></label>
<label data-tooltip="Inbox" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M3 13h5l2 3h4l2-3h5M5 5h14l2 8v6H3v-6z"/></svg><span class="sr-only">Inbox</span><i aria-hidden="true" class="absolute top-[9px] right-[9px] w-2 h-2 rounded-full bg-tsl14-alert shadow-[0_0_0_2.5px_var(--color-tsl14-panel)]"></i></label>
<label data-tooltip="Projects" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span class="sr-only">Projects</span></label>
<label data-tooltip="Analytics" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M4 20V10m6 10V4m6 16v-7"/></svg><span class="sr-only">Analytics</span></label>
</nav>
<span data-tooltip="Ada Kim" class="relative grid place-items-center w-11 h-11 mt-auto rounded-full text-[11.5px] font-bold text-white cursor-pointer bg-[linear-gradient(135deg,var(--color-tsl14-accent),oklch(0.6_0.17_285))] after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0">AK</span>
</aside>
<main class="p-8 overflow-y-auto [scrollbar-gutter:stable]">
<h1 class="text-[22px] font-semibold leading-tight mb-2.5">Home</h1>
<p class="max-w-[46ch] text-sm leading-relaxed text-tsl14-mut text-pretty">Click the rail icons — the active pill slides between them with CSS anchor positioning. Hover for tooltips.</p>
</main>
</section><section class="tsl-14 w-full h-svh min-h-svh grid grid-cols-[68px_1fr] font-tsl14-sans bg-tsl14-bg text-tsl14-ink">
<aside aria-label="Primary" class="relative flex flex-col items-center gap-1.5 py-3.5 bg-tsl14-panel border-r border-tsl14-line">
<span aria-hidden="true" class="w-[30px] h-[30px] mb-3 rounded-[9px] bg-[conic-gradient(from_45deg,var(--color-tsl14-accent),oklch(0.62_0.17_285))]"></span>
<span aria-hidden="true" class="tsl-14-pill absolute left-2 w-[3.5px] h-[26px] rounded"></span>
<nav class="flex flex-col gap-1.5">
<label data-tooltip="Home" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" checked class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M3 10.5 12 3l9 7.5M5.5 9.5V21h13V9.5"/></svg><span class="sr-only">Home</span></label>
<label data-tooltip="Inbox" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M3 13h5l2 3h4l2-3h5M5 5h14l2 8v6H3v-6z"/></svg><span class="sr-only">Inbox</span><i aria-hidden="true" class="absolute top-[9px] right-[9px] w-2 h-2 rounded-full bg-tsl14-alert shadow-[0_0_0_2.5px_var(--color-tsl14-panel)]"></i></label>
<label data-tooltip="Projects" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span class="sr-only">Projects</span></label>
<label data-tooltip="Analytics" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M4 20V10m6 10V4m6 16v-7"/></svg><span class="sr-only">Analytics</span></label>
</nav>
<span data-tooltip="Ada Kim" class="relative grid place-items-center w-11 h-11 mt-auto rounded-full text-[11.5px] font-bold text-white cursor-pointer bg-[linear-gradient(135deg,var(--color-tsl14-accent),oklch(0.6_0.17_285))] after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0">AK</span>
</aside>
<main class="p-8 overflow-y-auto [scrollbar-gutter:stable]">
<h1 class="text-[22px] font-semibold leading-tight mb-2.5">Home</h1>
<p class="max-w-[46ch] text-sm leading-relaxed text-tsl14-mut text-pretty">Click the rail icons — the active pill slides between them with CSS anchor positioning. Hover for tooltips.</p>
</main>
</section>@import "tailwindcss";
@theme {
--color-tsl14-bg: #121519;
--color-tsl14-bg: oklch(0.13 0.015 255);
--color-tsl14-panel: #181b21;
--color-tsl14-panel: oklch(0.16 0.02 255);
--color-tsl14-tip: #262a32;
--color-tsl14-tip: oklch(0.24 0.02 255);
--color-tsl14-ink: #e9ebef;
--color-tsl14-ink: oklch(0.93 0.005 255);
--color-tsl14-mut: #82868f;
--color-tsl14-mut: oklch(0.58 0.014 255);
--color-tsl14-line: #373b44;
--color-tsl14-line: oklch(0.27 0.02 255);
--color-tsl14-accent: #4fc9e0;
--color-tsl14-accent: oklch(0.74 0.14 195);
--color-tsl14-alert: #e7603f;
--color-tsl14-alert: oklch(0.68 0.21 25);
--color-tsl14-tint: rgb(79 201 224 / 0.14);
--color-tsl14-tint: color-mix(in oklab, oklch(0.74 0.14 195) 14%, transparent);
--color-tsl14-glow: rgb(79 201 224 / 0.6);
--color-tsl14-glow: color-mix(in oklab, oklch(0.74 0.14 195) 60%, transparent);
--font-tsl14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-14 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: dark;
}
/* Anchor names are identifiers, not design values — no utility mints them,
and the sliding pill is pure enhancement over the has-checked: tint. */
.tsl-14 nav label:nth-child(1) {
anchor-name: --tsl14-a1;
}
.tsl-14 nav label:nth-child(2) {
anchor-name: --tsl14-a2;
}
.tsl-14 nav label:nth-child(3) {
anchor-name: --tsl14-a3;
}
.tsl-14 nav label:nth-child(4) {
anchor-name: --tsl14-a4;
}
.tsl-14-pill {
background: var(--color-tsl14-accent);
box-shadow: 0 0 10px var(--color-tsl14-glow);
position-anchor: --tsl14-a1;
top: calc(anchor(top) + 9px);
transition: top .3s cubic-bezier(.5,0,.2,1.2);
}
.tsl-14:has(nav label:nth-child(2) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a2;
}
.tsl-14:has(nav label:nth-child(3) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a3;
}
.tsl-14:has(nav label:nth-child(4) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a4;
}
@supports not (anchor-name: --x) {
.tsl-14-pill {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
.tsl-14-pill {
transition: none;
}
}@import "tailwindcss";
@theme {
--color-tsl14-bg: #121519;
--color-tsl14-bg: oklch(0.13 0.015 255);
--color-tsl14-panel: #181b21;
--color-tsl14-panel: oklch(0.16 0.02 255);
--color-tsl14-tip: #262a32;
--color-tsl14-tip: oklch(0.24 0.02 255);
--color-tsl14-ink: #e9ebef;
--color-tsl14-ink: oklch(0.93 0.005 255);
--color-tsl14-mut: #82868f;
--color-tsl14-mut: oklch(0.58 0.014 255);
--color-tsl14-line: #373b44;
--color-tsl14-line: oklch(0.27 0.02 255);
--color-tsl14-accent: #4fc9e0;
--color-tsl14-accent: oklch(0.74 0.14 195);
--color-tsl14-alert: #e7603f;
--color-tsl14-alert: oklch(0.68 0.21 25);
--color-tsl14-tint: rgb(79 201 224 / 0.14);
--color-tsl14-tint: color-mix(in oklab, oklch(0.74 0.14 195) 14%, transparent);
--color-tsl14-glow: rgb(79 201 224 / 0.6);
--color-tsl14-glow: color-mix(in oklab, oklch(0.74 0.14 195) 60%, transparent);
--font-tsl14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-14 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: dark;
}
/* Anchor names are identifiers, not design values — no utility mints them,
and the sliding pill is pure enhancement over the has-checked: tint. */
.tsl-14 nav label:nth-child(1) {
anchor-name: --tsl14-a1;
}
.tsl-14 nav label:nth-child(2) {
anchor-name: --tsl14-a2;
}
.tsl-14 nav label:nth-child(3) {
anchor-name: --tsl14-a3;
}
.tsl-14 nav label:nth-child(4) {
anchor-name: --tsl14-a4;
}
.tsl-14-pill {
background: var(--color-tsl14-accent);
box-shadow: 0 0 10px var(--color-tsl14-glow);
position-anchor: --tsl14-a1;
top: calc(anchor(top) + 9px);
transition: top .3s cubic-bezier(.5,0,.2,1.2);
}
.tsl-14:has(nav label:nth-child(2) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a2;
}
.tsl-14:has(nav label:nth-child(3) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a3;
}
.tsl-14:has(nav label:nth-child(4) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a4;
}
@supports not (anchor-name: --x) {
.tsl-14-pill {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
.tsl-14-pill {
transition: none;
}
}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-tsl14-bg: #121519;
--color-tsl14-bg: oklch(0.13 0.015 255);
--color-tsl14-panel: #181b21;
--color-tsl14-panel: oklch(0.16 0.02 255);
--color-tsl14-tip: #262a32;
--color-tsl14-tip: oklch(0.24 0.02 255);
--color-tsl14-ink: #e9ebef;
--color-tsl14-ink: oklch(0.93 0.005 255);
--color-tsl14-mut: #82868f;
--color-tsl14-mut: oklch(0.58 0.014 255);
--color-tsl14-line: #373b44;
--color-tsl14-line: oklch(0.27 0.02 255);
--color-tsl14-accent: #4fc9e0;
--color-tsl14-accent: oklch(0.74 0.14 195);
--color-tsl14-alert: #e7603f;
--color-tsl14-alert: oklch(0.68 0.21 25);
--color-tsl14-tint: rgb(79 201 224 / 0.14);
--color-tsl14-tint: color-mix(in oklab, oklch(0.74 0.14 195) 14%, transparent);
--color-tsl14-glow: rgb(79 201 224 / 0.6);
--color-tsl14-glow: color-mix(in oklab, oklch(0.74 0.14 195) 60%, transparent);
--font-tsl14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-14 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: dark;
}
/* Anchor names are identifiers, not design values — no utility mints them,
and the sliding pill is pure enhancement over the has-checked: tint. */
.tsl-14 nav label:nth-child(1) {
anchor-name: --tsl14-a1;
}
.tsl-14 nav label:nth-child(2) {
anchor-name: --tsl14-a2;
}
.tsl-14 nav label:nth-child(3) {
anchor-name: --tsl14-a3;
}
.tsl-14 nav label:nth-child(4) {
anchor-name: --tsl14-a4;
}
.tsl-14-pill {
background: var(--color-tsl14-accent);
box-shadow: 0 0 10px var(--color-tsl14-glow);
position-anchor: --tsl14-a1;
top: calc(anchor(top) + 9px);
transition: top .3s cubic-bezier(.5,0,.2,1.2);
}
.tsl-14:has(nav label:nth-child(2) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a2;
}
.tsl-14:has(nav label:nth-child(3) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a3;
}
.tsl-14:has(nav label:nth-child(4) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a4;
}
@supports not (anchor-name: --x) {
.tsl-14-pill {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
.tsl-14-pill {
transition: none;
}
}@import "tailwindcss";
@theme {
--color-tsl14-bg: #121519;
--color-tsl14-bg: oklch(0.13 0.015 255);
--color-tsl14-panel: #181b21;
--color-tsl14-panel: oklch(0.16 0.02 255);
--color-tsl14-tip: #262a32;
--color-tsl14-tip: oklch(0.24 0.02 255);
--color-tsl14-ink: #e9ebef;
--color-tsl14-ink: oklch(0.93 0.005 255);
--color-tsl14-mut: #82868f;
--color-tsl14-mut: oklch(0.58 0.014 255);
--color-tsl14-line: #373b44;
--color-tsl14-line: oklch(0.27 0.02 255);
--color-tsl14-accent: #4fc9e0;
--color-tsl14-accent: oklch(0.74 0.14 195);
--color-tsl14-alert: #e7603f;
--color-tsl14-alert: oklch(0.68 0.21 25);
--color-tsl14-tint: rgb(79 201 224 / 0.14);
--color-tsl14-tint: color-mix(in oklab, oklch(0.74 0.14 195) 14%, transparent);
--color-tsl14-glow: rgb(79 201 224 / 0.6);
--color-tsl14-glow: color-mix(in oklab, oklch(0.74 0.14 195) 60%, transparent);
--font-tsl14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-14 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: dark;
}
/* Anchor names are identifiers, not design values — no utility mints them,
and the sliding pill is pure enhancement over the has-checked: tint. */
.tsl-14 nav label:nth-child(1) {
anchor-name: --tsl14-a1;
}
.tsl-14 nav label:nth-child(2) {
anchor-name: --tsl14-a2;
}
.tsl-14 nav label:nth-child(3) {
anchor-name: --tsl14-a3;
}
.tsl-14 nav label:nth-child(4) {
anchor-name: --tsl14-a4;
}
.tsl-14-pill {
background: var(--color-tsl14-accent);
box-shadow: 0 0 10px var(--color-tsl14-glow);
position-anchor: --tsl14-a1;
top: calc(anchor(top) + 9px);
transition: top .3s cubic-bezier(.5,0,.2,1.2);
}
.tsl-14:has(nav label:nth-child(2) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a2;
}
.tsl-14:has(nav label:nth-child(3) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a3;
}
.tsl-14:has(nav label:nth-child(4) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a4;
}
@supports not (anchor-name: --x) {
.tsl-14-pill {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
.tsl-14-pill {
transition: none;
}
}<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=Plus+Jakarta+Sans:wght@400;500;600;700;800&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=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- This is the collection's clearest split. Everything about the rail — sizing, tint, tooltip, focus ring, dot — is utilities; everything about the pill is CSS, because anchor positioning trades in identifiers and
@supportsguards, neither of which belongs in a class list. has-[:focus-visible]:is what makes a label wrapping an invisible radio focusable-looking. It is worth knowing as the generic form:has-checked:is just sugar for this shape.- UI change from the CSS original: the avatar grew from 36px to 44px (it is a real control with a tooltip, so it owes the same target size as the nav items), and the tooltip now also fires on keyboard focus for every item rather than hover alone.
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 Mini Icon Rail Sidebar with Anchor-Positioned Active Pill
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-mini-icon-rail-anchor-positioned-active-pill/
The 68px icon rail perfected: 44px targets, hover tooltips, notification dots, a bottom-pinned avatar — and an active pill that glides between icons using CSS anchor positioning. The effect teams reach for a motion library to get, with no script and a clean fallback.
## HTML
```html
<section class="tsl-14 w-full h-svh min-h-svh grid grid-cols-[68px_1fr] font-tsl14-sans bg-tsl14-bg text-tsl14-ink">
<aside aria-label="Primary" class="relative flex flex-col items-center gap-1.5 py-3.5 bg-tsl14-panel border-r border-tsl14-line">
<span aria-hidden="true" class="w-[30px] h-[30px] mb-3 rounded-[9px] bg-[conic-gradient(from_45deg,var(--color-tsl14-accent),oklch(0.62_0.17_285))]"></span>
<span aria-hidden="true" class="tsl-14-pill absolute left-2 w-[3.5px] h-[26px] rounded"></span>
<nav class="flex flex-col gap-1.5">
<label data-tooltip="Home" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" checked class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M3 10.5 12 3l9 7.5M5.5 9.5V21h13V9.5"/></svg><span class="sr-only">Home</span></label>
<label data-tooltip="Inbox" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M3 13h5l2 3h4l2-3h5M5 5h14l2 8v6H3v-6z"/></svg><span class="sr-only">Inbox</span><i aria-hidden="true" class="absolute top-[9px] right-[9px] w-2 h-2 rounded-full bg-tsl14-alert shadow-[0_0_0_2.5px_var(--color-tsl14-panel)]"></i></label>
<label data-tooltip="Projects" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span class="sr-only">Projects</span></label>
<label data-tooltip="Analytics" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M4 20V10m6 10V4m6 16v-7"/></svg><span class="sr-only">Analytics</span></label>
</nav>
<span data-tooltip="Ada Kim" class="relative grid place-items-center w-11 h-11 mt-auto rounded-full text-[11.5px] font-bold text-white cursor-pointer bg-[linear-gradient(135deg,var(--color-tsl14-accent),oklch(0.6_0.17_285))] after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0">AK</span>
</aside>
<main class="p-8 overflow-y-auto [scrollbar-gutter:stable]">
<h1 class="text-[22px] font-semibold leading-tight mb-2.5">Home</h1>
<p class="max-w-[46ch] text-sm leading-relaxed text-tsl14-mut text-pretty">Click the rail icons — the active pill slides between them with CSS anchor positioning. Hover for tooltips.</p>
</main>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsl14-bg: #121519;
--color-tsl14-bg: oklch(0.13 0.015 255);
--color-tsl14-panel: #181b21;
--color-tsl14-panel: oklch(0.16 0.02 255);
--color-tsl14-tip: #262a32;
--color-tsl14-tip: oklch(0.24 0.02 255);
--color-tsl14-ink: #e9ebef;
--color-tsl14-ink: oklch(0.93 0.005 255);
--color-tsl14-mut: #82868f;
--color-tsl14-mut: oklch(0.58 0.014 255);
--color-tsl14-line: #373b44;
--color-tsl14-line: oklch(0.27 0.02 255);
--color-tsl14-accent: #4fc9e0;
--color-tsl14-accent: oklch(0.74 0.14 195);
--color-tsl14-alert: #e7603f;
--color-tsl14-alert: oklch(0.68 0.21 25);
--color-tsl14-tint: rgb(79 201 224 / 0.14);
--color-tsl14-tint: color-mix(in oklab, oklch(0.74 0.14 195) 14%, transparent);
--color-tsl14-glow: rgb(79 201 224 / 0.6);
--color-tsl14-glow: color-mix(in oklab, oklch(0.74 0.14 195) 60%, transparent);
--font-tsl14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-14 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: dark;
}
/* Anchor names are identifiers, not design values — no utility mints them,
and the sliding pill is pure enhancement over the has-checked: tint. */
.tsl-14 nav label:nth-child(1) {
anchor-name: --tsl14-a1;
}
.tsl-14 nav label:nth-child(2) {
anchor-name: --tsl14-a2;
}
.tsl-14 nav label:nth-child(3) {
anchor-name: --tsl14-a3;
}
.tsl-14 nav label:nth-child(4) {
anchor-name: --tsl14-a4;
}
.tsl-14-pill {
background: var(--color-tsl14-accent);
box-shadow: 0 0 10px var(--color-tsl14-glow);
position-anchor: --tsl14-a1;
top: calc(anchor(top) + 9px);
transition: top .3s cubic-bezier(.5,0,.2,1.2);
}
.tsl-14:has(nav label:nth-child(2) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a2;
}
.tsl-14:has(nav label:nth-child(3) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a3;
}
.tsl-14:has(nav label:nth-child(4) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a4;
}
@supports not (anchor-name: --x) {
.tsl-14-pill {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
.tsl-14-pill {
transition: none;
}
}
```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 Mini Icon Rail Sidebar with Anchor-Positioned Active Pill
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-mini-icon-rail-anchor-positioned-active-pill/
The 68px icon rail perfected: 44px targets, hover tooltips, notification dots, a bottom-pinned avatar — and an active pill that glides between icons using CSS anchor positioning. The effect teams reach for a motion library to get, with no script and a clean fallback.
## HTML
```html
<section class="tsl-14 w-full h-svh min-h-svh grid grid-cols-[68px_1fr] font-tsl14-sans bg-tsl14-bg text-tsl14-ink">
<aside aria-label="Primary" class="relative flex flex-col items-center gap-1.5 py-3.5 bg-tsl14-panel border-r border-tsl14-line">
<span aria-hidden="true" class="w-[30px] h-[30px] mb-3 rounded-[9px] bg-[conic-gradient(from_45deg,var(--color-tsl14-accent),oklch(0.62_0.17_285))]"></span>
<span aria-hidden="true" class="tsl-14-pill absolute left-2 w-[3.5px] h-[26px] rounded"></span>
<nav class="flex flex-col gap-1.5">
<label data-tooltip="Home" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" checked class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M3 10.5 12 3l9 7.5M5.5 9.5V21h13V9.5"/></svg><span class="sr-only">Home</span></label>
<label data-tooltip="Inbox" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M3 13h5l2 3h4l2-3h5M5 5h14l2 8v6H3v-6z"/></svg><span class="sr-only">Inbox</span><i aria-hidden="true" class="absolute top-[9px] right-[9px] w-2 h-2 rounded-full bg-tsl14-alert shadow-[0_0_0_2.5px_var(--color-tsl14-panel)]"></i></label>
<label data-tooltip="Projects" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span class="sr-only">Projects</span></label>
<label data-tooltip="Analytics" class="relative z-[1] grid place-items-center w-11 h-11 rounded-xl text-tsl14-mut cursor-pointer transition-colors duration-200 hover:text-tsl14-ink has-checked:text-tsl14-accent has-checked:bg-tsl14-tint has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl14-accent has-[:focus-visible]:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0 has-[:focus-visible]:after:opacity-100 has-[:focus-visible]:after:translate-x-0"><input type="radio" name="tsl14" class="absolute inset-0 m-0 opacity-0 cursor-pointer"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M4 20V10m6 10V4m6 16v-7"/></svg><span class="sr-only">Analytics</span></label>
</nav>
<span data-tooltip="Ada Kim" class="relative grid place-items-center w-11 h-11 mt-auto rounded-full text-[11.5px] font-bold text-white cursor-pointer bg-[linear-gradient(135deg,var(--color-tsl14-accent),oklch(0.6_0.17_285))] after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+12px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl14-tip after:border after:border-tsl14-line after:text-tsl14-ink after:text-xs after:font-medium after:whitespace-nowrap after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-[250ms] after:z-10 hover:after:opacity-100 hover:after:translate-x-0">AK</span>
</aside>
<main class="p-8 overflow-y-auto [scrollbar-gutter:stable]">
<h1 class="text-[22px] font-semibold leading-tight mb-2.5">Home</h1>
<p class="max-w-[46ch] text-sm leading-relaxed text-tsl14-mut text-pretty">Click the rail icons — the active pill slides between them with CSS anchor positioning. Hover for tooltips.</p>
</main>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsl14-bg: #121519;
--color-tsl14-bg: oklch(0.13 0.015 255);
--color-tsl14-panel: #181b21;
--color-tsl14-panel: oklch(0.16 0.02 255);
--color-tsl14-tip: #262a32;
--color-tsl14-tip: oklch(0.24 0.02 255);
--color-tsl14-ink: #e9ebef;
--color-tsl14-ink: oklch(0.93 0.005 255);
--color-tsl14-mut: #82868f;
--color-tsl14-mut: oklch(0.58 0.014 255);
--color-tsl14-line: #373b44;
--color-tsl14-line: oklch(0.27 0.02 255);
--color-tsl14-accent: #4fc9e0;
--color-tsl14-accent: oklch(0.74 0.14 195);
--color-tsl14-alert: #e7603f;
--color-tsl14-alert: oklch(0.68 0.21 25);
--color-tsl14-tint: rgb(79 201 224 / 0.14);
--color-tsl14-tint: color-mix(in oklab, oklch(0.74 0.14 195) 14%, transparent);
--color-tsl14-glow: rgb(79 201 224 / 0.6);
--color-tsl14-glow: color-mix(in oklab, oklch(0.74 0.14 195) 60%, transparent);
--font-tsl14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-14 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: dark;
}
/* Anchor names are identifiers, not design values — no utility mints them,
and the sliding pill is pure enhancement over the has-checked: tint. */
.tsl-14 nav label:nth-child(1) {
anchor-name: --tsl14-a1;
}
.tsl-14 nav label:nth-child(2) {
anchor-name: --tsl14-a2;
}
.tsl-14 nav label:nth-child(3) {
anchor-name: --tsl14-a3;
}
.tsl-14 nav label:nth-child(4) {
anchor-name: --tsl14-a4;
}
.tsl-14-pill {
background: var(--color-tsl14-accent);
box-shadow: 0 0 10px var(--color-tsl14-glow);
position-anchor: --tsl14-a1;
top: calc(anchor(top) + 9px);
transition: top .3s cubic-bezier(.5,0,.2,1.2);
}
.tsl-14:has(nav label:nth-child(2) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a2;
}
.tsl-14:has(nav label:nth-child(3) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a3;
}
.tsl-14:has(nav label:nth-child(4) input:checked) .tsl-14-pill {
position-anchor: --tsl14-a4;
}
@supports not (anchor-name: --x) {
.tsl-14-pill {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
.tsl-14-pill {
transition: none;
}
}
```How this works
The pill is a shared layout element, natively. Each item declares an anchor name; the pill re-points position-anchor when a different radio is checked, and because its top is a transitioned anchor() expression, re-targeting makes it slide. The whole mechanism is four short CSS rules and no markup beyond one absolutely-positioned span.
The fallback is the design, not a degradation. Every item also carries has-checked: colour and tint utilities, so in Firefox the rail reads as a perfectly normal, perfectly clear icon nav; @supports not (anchor-name:--x) simply hides the pill.
Make it yours
- Rail widths: 56px for dense pro tools, 68px default, 80px touch-first.
- Tooltip delay: 250ms stops flicker as the cursor travels; 500ms for rails beside a frequent mouse path.
- Dots to counts: swap the 8px dot for a
min-w-4pill at 9.5px and you have numeric badges.
Gotchas — read before shipping
- Ship the non-anchor active state first and treat the pill as bonus — Firefox has no anchor positioning yet.
- Icon-only nav MUST keep an accessible name: each label here wraps its radio and carries a
sr-onlyspan as well as the tooltip attribute. - The notification dot needs a 2.5px ring in the rail colour, or it merges visually with the icon strokes underneath.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 125+ | 26+ | — | 125+ |
The sliding pill needs CSS anchor positioning (Firefox in development); the rail with static active states works back to 2017.