30 Tailwind CSS Sidebar Layouts
25 / 30

All 30 in 30 Tailwind CSS Sidebar Layouts

  1. 01Tailwind SaaS Dashboard Sidebar with Native Dark Mode Switch
  2. 02Tailwind Collapsible Admin Sidebar with Icon Tooltips
  3. 03Tailwind Documentation Sidebar with Multi-Level :target Tree
  4. 04Tailwind Responsive Sidebar to Bottom Navigation Switch Layout
  5. 05Tailwind Analytics Sidebar with CSS Subgrid Column Alignment
  6. 06Tailwind Animated Collapsible Sidebar with Icon-Only Mode
  7. 07Tailwind Sticky Sidebar Layout with Scroll-Driven Progress Bar
  8. 08Tailwind Pure CSS Collapsible Sidebar Drawer without JavaScript
  9. 09Tailwind Fixed Sidebar Layout with Independently Scrolling Content
  10. 10Tailwind Off-Canvas Navigation Drawer with the Popover API
  11. 11Tailwind Right-Aligned Sidebar Layout for Inspector Panels
  12. 12Tailwind Two-Pane Webmail Layout with List and Reading Pane
  13. 13Tailwind Dual Sidebar App Shell with Left Nav and Context Panel
  14. 14Tailwind Mini Icon Rail Sidebar with Anchor-Positioned Active Pill
  15. 15Tailwind E-Commerce Filter Sidebar with Native Details Accordion
  16. 16Tailwind Floating Glassmorphism Sidebar with Gradient Hairline
  17. 17Tailwind Sidebar Menu with Pinned User Profile Footer
  18. 18Tailwind Push-Content Sidebar Drawer with GPU Transitions
  19. 19Tailwind Off-Canvas Cart Overlay with Blurred Backdrop Scrim
  20. 20Tailwind Sidebar with Persistent Collapsed State in localStorage
  21. 21Tailwind Sidebar with Sticky Search Bar Inside the Rail
  22. 22Tailwind Section-Grouped Sidebar Navigation with Header Labels
  23. 23Tailwind Sliding Active Indicator Sidebar with CSS :target
  24. 24Tailwind Multi-Level Nested Tree Sidebar with Details Branches
  25. 25Tailwind Tabbed Sidebar with Radio-Driven Panel Switching
  26. 26Tailwind Onboarding Wizard Sidebar with Step Progress Tracking
  27. 27Tailwind Resizable Sidebar Layout Using the CSS Resize Property
  28. 28Tailwind Three-Pane Workspace Shell with Icon Rail and List
  29. 29Tailwind Adaptive Sidebar Layout Using CSS Container Queries
  30. 30Tailwind Frosted Glass Floating Sidebar with Animated Aurora
Pure TailwindMIT licensed

Tailwind Tabbed Sidebar with Radio-Driven Panel Switching

One sidebar, three personalities: Files, Search and Activity, switched by a segmented control. Hidden radios drive which panel renders, a thumb glides between tabs, and panels enter with a few pixels of lift — with native arrow-key switching for free.

Live Demo
Plain CSSTailwind v4
Try it

Mosaic by Cruip: a complete Tailwind admin dashboard template in HTML, React, Next.js and Vue · $69

Cruip · Mosaic by Cruip · Affiliate link

View Mosaic dashboard

The code

<section class="tsl-25 w-full h-svh min-h-svh grid grid-cols-[272px_1fr] font-tsl25-sans bg-tsl25-bg text-tsl25-ink">
  <aside class="flex flex-col overflow-hidden bg-tsl25-panel border-r border-tsl25-line px-3 py-3.5">
    <input type="radio" name="tsl25" id="tsl25-t1" checked class="sr-only"><input type="radio" name="tsl25" id="tsl25-t2" class="sr-only"><input type="radio" name="tsl25" id="tsl25-t3" class="sr-only">
    <div role="group" aria-label="Sidebar panels" class="relative grid grid-cols-3 p-1 rounded-[11px] bg-tsl25-well border border-tsl25-line has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl25-accent has-[:focus-visible]:outline-offset-2">
      <div aria-hidden="true" class="tsl-25-thumb absolute top-1 bottom-1 left-1 w-[calc((100%-8px)/3)] rounded-lg bg-tsl25-thumb border border-tsl25-thumbline translate-x-0 transition-transform duration-[280ms] ease-[cubic-bezier(.5,0,.2,1.1)] motion-reduce:transition-none"></div>
      <label for="tsl25-t1" class="tsl-25-tab relative z-[1] grid place-items-center min-h-11 rounded-lg text-[12.5px] font-semibold text-tsl25-mut cursor-pointer transition-colors duration-200">Files</label>
      <label for="tsl25-t2" class="tsl-25-tab relative z-[1] grid place-items-center min-h-11 rounded-lg text-[12.5px] font-semibold text-tsl25-mut cursor-pointer transition-colors duration-200">Search</label>
      <label for="tsl25-t3" class="tsl-25-tab relative z-[1] grid place-items-center min-h-11 rounded-lg text-[12.5px] font-semibold text-tsl25-mut cursor-pointer transition-colors duration-200">Activity</label>
    </div>
    <div data-panel="1" class="tsl-25-panel hidden flex-col gap-0.5 pt-3.5 min-h-0 overflow-y-auto animate-tsl25-in motion-reduce:animate-none">
      <a href="#" aria-current="page" class="flex items-center min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl25-mut no-underline transition-colors duration-150 hover:text-tsl25-ink hover:bg-tsl25-hover aria-[current=page]:text-tsl25-accent aria-[current=page]:bg-tsl25-tint focus-visible:outline-2 focus-visible:outline-tsl25-accent focus-visible:outline-offset-1">proposal-v3.md</a>
      <a href="#" class="flex items-center min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl25-mut no-underline transition-colors duration-150 hover:text-tsl25-ink hover:bg-tsl25-hover focus-visible:outline-2 focus-visible:outline-tsl25-accent focus-visible:outline-offset-1">budget-2027.xlsx</a>
      <a href="#" class="flex items-center min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl25-mut no-underline transition-colors duration-150 hover:text-tsl25-ink hover:bg-tsl25-hover focus-visible:outline-2 focus-visible:outline-tsl25-accent focus-visible:outline-offset-1">kickoff-notes.md</a>
      <a href="#" class="flex items-center min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl25-mut no-underline transition-colors duration-150 hover:text-tsl25-ink hover:bg-tsl25-hover focus-visible:outline-2 focus-visible:outline-tsl25-accent focus-visible:outline-offset-1">brand-audit.pdf</a>
    </div>
    <div data-panel="2" class="tsl-25-panel hidden flex-col gap-0.5 pt-3.5 min-h-0 overflow-y-auto animate-tsl25-in motion-reduce:animate-none">
      <label class="grid grid-cols-[auto_1fr] items-center gap-2 h-11 px-3 mb-2.5 rounded-[10px] border border-tsl25-line bg-tsl25-well focus-within:border-tsl25-accent focus-within:shadow-[0_0_0_3px_var(--color-tsl25-ring)]"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-[15px] h-[15px] fill-none stroke-tsl25-mut stroke-2 [stroke-linecap:round]"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><input type="search" placeholder="Search workspace&hellip;" aria-label="Search workspace" class="min-w-0 border-0 bg-transparent text-tsl25-ink text-[13px] outline-none placeholder:text-tsl25-placeholder"></label>
      <span class="px-3 pt-1.5 pb-1 text-[10.5px] font-semibold uppercase tracking-[0.08em] text-tsl25-mut">Recent</span>
      <a href="#" class="flex items-center min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl25-mut no-underline transition-colors duration-150 hover:text-tsl25-ink hover:bg-tsl25-hover focus-visible:outline-2 focus-visible:outline-tsl25-accent focus-visible:outline-offset-1">container queries</a>
      <a href="#" class="flex items-center min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl25-mut no-underline transition-colors duration-150 hover:text-tsl25-ink hover:bg-tsl25-hover focus-visible:outline-2 focus-visible:outline-tsl25-accent focus-visible:outline-offset-1">q3 roadmap</a>
    </div>
    <div data-panel="3" class="tsl-25-panel hidden flex-col pt-3.5 min-h-0 overflow-y-auto animate-tsl25-in motion-reduce:animate-none">
      <p class="px-3 py-2.5 border-b border-tsl25-line text-[13px] leading-[1.5] text-tsl25-mut"><strong class="font-semibold text-tsl25-ink">Priya</strong> edited proposal-v3 <time class="float-right text-[11px]">12m</time></p>
      <p class="px-3 py-2.5 border-b border-tsl25-line text-[13px] leading-[1.5] text-tsl25-mut"><strong class="font-semibold text-tsl25-ink">Marcus</strong> commented <time class="float-right text-[11px]">1h</time></p>
      <p class="px-3 py-2.5 border-b border-tsl25-line text-[13px] leading-[1.5] text-tsl25-mut"><strong class="font-semibold text-tsl25-ink">Amara</strong> uploaded 2 files <time class="float-right text-[11px]">3h</time></p>
    </div>
  </aside>
  <main class="p-8 overflow-y-auto [scrollbar-gutter:stable]">
    <h1 class="text-[22px] font-semibold leading-tight mb-2.5">proposal-v3.md</h1>
    <p class="max-w-[46ch] text-sm leading-relaxed text-tsl25-mut text-pretty">Switch tabs &mdash; the thumb glides, panels cross-fade. Radios give the group arrow-key switching natively.</p>
  </main>
</section>
@import "tailwindcss";

@theme {
  --color-tsl25-bg: #171210;
  --color-tsl25-bg: oklch(0.13 0.013 35);
  --color-tsl25-panel: #201a16;
  --color-tsl25-panel: oklch(0.17 0.016 35);
  --color-tsl25-well: #1a1512;
  --color-tsl25-well: oklch(0.14 0.014 35);
  --color-tsl25-ink: #f2ece7;
  --color-tsl25-ink: oklch(0.94 0.006 35);
  --color-tsl25-mut: #a3968c;
  --color-tsl25-mut: oklch(0.65 0.014 35);
  --color-tsl25-placeholder: #8a7d73;
  --color-tsl25-placeholder: oklch(0.57 0.014 35);
  --color-tsl25-line: #3d342e;
  --color-tsl25-line: oklch(0.28 0.018 35);
  --color-tsl25-accent: #e0904f;
  --color-tsl25-accent: oklch(0.72 0.15 40);
  --color-tsl25-thumb: rgb(224 144 79 / 0.22);
  --color-tsl25-thumb: color-mix(in oklab, oklch(0.72 0.15 40) 22%, transparent);
  --color-tsl25-thumbline: rgb(224 144 79 / 0.4);
  --color-tsl25-thumbline: color-mix(in oklab, oklch(0.72 0.15 40) 40%, transparent);
  --color-tsl25-ring: rgb(224 144 79 / 0.2);
  --color-tsl25-ring: color-mix(in oklab, oklch(0.72 0.15 40) 20%, transparent);
  --color-tsl25-hover: rgb(242 236 231 / 0.06);
  --color-tsl25-hover: color-mix(in oklab, oklch(0.94 0.006 35) 6%, transparent);
  --color-tsl25-tint: rgb(224 144 79 / 0.12);
  --color-tsl25-tint: color-mix(in oklab, oklch(0.72 0.15 40) 12%, transparent);
  --font-tsl25-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --animate-tsl25-in: tsl25-in .25s ease;
}

@keyframes tsl25-in {
  from {
    opacity: 0;
    translate: 0 4px;
  }
}

.tsl-25 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: dark;
}
/* STATE — thumb position, tab colour and which panel is displayed.
   display, not opacity: a hidden panel must leave the tab order. */

#tsl25-t2:checked ~ [role="group"] .tsl-25-thumb {
  translate: 100% 0;
}

#tsl25-t3:checked ~ [role="group"] .tsl-25-thumb {
  translate: 200% 0;
}

#tsl25-t1:checked ~ [role="group"] label[for="tsl25-t1"],
#tsl25-t2:checked ~ [role="group"] label[for="tsl25-t2"],
#tsl25-t3:checked ~ [role="group"] label[for="tsl25-t3"] {
  color: var(--color-tsl25-ink);
}

#tsl25-t1:checked ~ [data-panel="1"],
#tsl25-t2:checked ~ [data-panel="2"],
#tsl25-t3:checked ~ [data-panel="3"] {
  display: flex;
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<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.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tsl25-bg: #171210;
  --color-tsl25-bg: oklch(0.13 0.013 35);
  --color-tsl25-panel: #201a16;
  --color-tsl25-panel: oklch(0.17 0.016 35);
  --color-tsl25-well: #1a1512;
  --color-tsl25-well: oklch(0.14 0.014 35);
  --color-tsl25-ink: #f2ece7;
  --color-tsl25-ink: oklch(0.94 0.006 35);
  --color-tsl25-mut: #a3968c;
  --color-tsl25-mut: oklch(0.65 0.014 35);
  --color-tsl25-placeholder: #8a7d73;
  --color-tsl25-placeholder: oklch(0.57 0.014 35);
  --color-tsl25-line: #3d342e;
  --color-tsl25-line: oklch(0.28 0.018 35);
  --color-tsl25-accent: #e0904f;
  --color-tsl25-accent: oklch(0.72 0.15 40);
  --color-tsl25-thumb: rgb(224 144 79 / 0.22);
  --color-tsl25-thumb: color-mix(in oklab, oklch(0.72 0.15 40) 22%, transparent);
  --color-tsl25-thumbline: rgb(224 144 79 / 0.4);
  --color-tsl25-thumbline: color-mix(in oklab, oklch(0.72 0.15 40) 40%, transparent);
  --color-tsl25-ring: rgb(224 144 79 / 0.2);
  --color-tsl25-ring: color-mix(in oklab, oklch(0.72 0.15 40) 20%, transparent);
  --color-tsl25-hover: rgb(242 236 231 / 0.06);
  --color-tsl25-hover: color-mix(in oklab, oklch(0.94 0.006 35) 6%, transparent);
  --color-tsl25-tint: rgb(224 144 79 / 0.12);
  --color-tsl25-tint: color-mix(in oklab, oklch(0.72 0.15 40) 12%, transparent);
  --font-tsl25-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --animate-tsl25-in: tsl25-in .25s ease;
}

@keyframes tsl25-in {
  from {
    opacity: 0;
    translate: 0 4px;
  }
}

.tsl-25 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: dark;
}
/* STATE — thumb position, tab colour and which panel is displayed.
   display, not opacity: a hidden panel must leave the tab order. */

#tsl25-t2:checked ~ [role="group"] .tsl-25-thumb {
  translate: 100% 0;
}

#tsl25-t3:checked ~ [role="group"] .tsl-25-thumb {
  translate: 200% 0;
}

#tsl25-t1:checked ~ [role="group"] label[for="tsl25-t1"],
#tsl25-t2:checked ~ [role="group"] label[for="tsl25-t2"],
#tsl25-t3:checked ~ [role="group"] label[for="tsl25-t3"] {
  color: var(--color-tsl25-ink);
}

#tsl25-t1:checked ~ [data-panel="1"],
#tsl25-t2:checked ~ [data-panel="2"],
#tsl25-t3:checked ~ [data-panel="3"] {
  display: flex;
}
3Fonts used by this demooptional — skip if you have your own type stack
<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.

  • The radios had to move out of the tab strip to sit before every element they drive — a markup change the CSS original did not need, and the price of keeping the switch declarative. Panels stay hidden in utilities; three sibling rules turn the chosen one back on.
  • UI change from the CSS original: tab labels and every panel row are min-h-11 (tabs were 34px), and the search field is 44px — a segmented control is one of the most-tapped elements in a sidebar.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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 Tabbed Sidebar with Radio-Driven Panel Switching
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-tabbed-sidebar-radio-panel-switching/

One sidebar, three personalities: Files, Search and Activity, switched by a segmented control. Hidden radios drive which panel renders, a thumb glides between tabs, and panels enter with a few pixels of lift &mdash; with native arrow-key switching for free.
## HTML
```html
<section class="tsl-25 w-full h-svh min-h-svh grid grid-cols-[272px_1fr] font-tsl25-sans bg-tsl25-bg text-tsl25-ink">
  <aside class="flex flex-col overflow-hidden bg-tsl25-panel border-r border-tsl25-line px-3 py-3.5">
    <input type="radio" name="tsl25" id="tsl25-t1" checked class="sr-only"><input type="radio" name="tsl25" id="tsl25-t2" class="sr-only"><input type="radio" name="tsl25" id="tsl25-t3" class="sr-only">
    <div role="group" aria-label="Sidebar panels" class="relative grid grid-cols-3 p-1 rounded-[11px] bg-tsl25-well border border-tsl25-line has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-tsl25-accent has-[:focus-visible]:outline-offset-2">
      <div aria-hidden="true" class="tsl-25-thumb absolute top-1 bottom-1 left-1 w-[calc((100%-8px)/3)] rounded-lg bg-tsl25-thumb border border-tsl25-thumbline translate-x-0 transition-transform duration-[280ms] ease-[cubic-bezier(.5,0,.2,1.1)] motion-reduce:transition-none"></div>
      <label for="tsl25-t1" class="tsl-25-tab relative z-[1] grid place-items-center min-h-11 rounded-lg text-[12.5px] font-semibold text-tsl25-mut cursor-pointer transition-colors duration-200">Files</label>
      <label for="tsl25-t2" class="tsl-25-tab relative z-[1] grid place-items-center min-h-11 rounded-lg text-[12.5px] font-semibold text-tsl25-mut cursor-pointer transition-colors duration-200">Search</label>
      <label for="tsl25-t3" class="tsl-25-tab relative z-[1] grid place-items-center min-h-11 rounded-lg text-[12.5px] font-semibold text-tsl25-mut cursor-pointer transition-colors duration-200">Activity</label>
    </div>
    <div data-panel="1" class="tsl-25-panel hidden flex-col gap-0.5 pt-3.5 min-h-0 overflow-y-auto animate-tsl25-in motion-reduce:animate-none">
      <a href="#" aria-current="page" class="flex items-center min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl25-mut no-underline transition-colors duration-150 hover:text-tsl25-ink hover:bg-tsl25-hover aria-[current=page]:text-tsl25-accent aria-[current=page]:bg-tsl25-tint focus-visible:outline-2 focus-visible:outline-tsl25-accent focus-visible:outline-offset-1">proposal-v3.md</a>
      <a href="#" class="flex items-center min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl25-mut no-underline transition-colors duration-150 hover:text-tsl25-ink hover:bg-tsl25-hover focus-visible:outline-2 focus-visible:outline-tsl25-accent focus-visible:outline-offset-1">budget-2027.xlsx</a>
      <a href="#" class="flex items-center min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl25-mut no-underline transition-colors duration-150 hover:text-tsl25-ink hover:bg-tsl25-hover focus-visible:outline-2 focus-visible:outline-tsl25-accent focus-visible:outline-offset-1">kickoff-notes.md</a>
      <a href="#" class="flex items-center min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl25-mut no-underline transition-colors duration-150 hover:text-tsl25-ink hover:bg-tsl25-hover focus-visible:outline-2 focus-visible:outline-tsl25-accent focus-visible:outline-offset-1">brand-audit.pdf</a>
    </div>
    <div data-panel="2" class="tsl-25-panel hidden flex-col gap-0.5 pt-3.5 min-h-0 overflow-y-auto animate-tsl25-in motion-reduce:animate-none">
      <label class="grid grid-cols-[auto_1fr] items-center gap-2 h-11 px-3 mb-2.5 rounded-[10px] border border-tsl25-line bg-tsl25-well focus-within:border-tsl25-accent focus-within:shadow-[0_0_0_3px_var(--color-tsl25-ring)]"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-[15px] h-[15px] fill-none stroke-tsl25-mut stroke-2 [stroke-linecap:round]"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><input type="search" placeholder="Search workspace&hellip;" aria-label="Search workspace" class="min-w-0 border-0 bg-transparent text-tsl25-ink text-[13px] outline-none placeholder:text-tsl25-placeholder"></label>
      <span class="px-3 pt-1.5 pb-1 text-[10.5px] font-semibold uppercase tracking-[0.08em] text-tsl25-mut">Recent</span>
      <a href="#" class="flex items-center min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl25-mut no-underline transition-colors duration-150 hover:text-tsl25-ink hover:bg-tsl25-hover focus-visible:outline-2 focus-visible:outline-tsl25-accent focus-visible:outline-offset-1">container queries</a>
      <a href="#" class="flex items-center min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl25-mut no-underline transition-colors duration-150 hover:text-tsl25-ink hover:bg-tsl25-hover focus-visible:outline-2 focus-visible:outline-tsl25-accent focus-visible:outline-offset-1">q3 roadmap</a>
    </div>
    <div data-panel="3" class="tsl-25-panel hidden flex-col pt-3.5 min-h-0 overflow-y-auto animate-tsl25-in motion-reduce:animate-none">
      <p class="px-3 py-2.5 border-b border-tsl25-line text-[13px] leading-[1.5] text-tsl25-mut"><strong class="font-semibold text-tsl25-ink">Priya</strong> edited proposal-v3 <time class="float-right text-[11px]">12m</time></p>
      <p class="px-3 py-2.5 border-b border-tsl25-line text-[13px] leading-[1.5] text-tsl25-mut"><strong class="font-semibold text-tsl25-ink">Marcus</strong> commented <time class="float-right text-[11px]">1h</time></p>
      <p class="px-3 py-2.5 border-b border-tsl25-line text-[13px] leading-[1.5] text-tsl25-mut"><strong class="font-semibold text-tsl25-ink">Amara</strong> uploaded 2 files <time class="float-right text-[11px]">3h</time></p>
    </div>
  </aside>
  <main class="p-8 overflow-y-auto [scrollbar-gutter:stable]">
    <h1 class="text-[22px] font-semibold leading-tight mb-2.5">proposal-v3.md</h1>
    <p class="max-w-[46ch] text-sm leading-relaxed text-tsl25-mut text-pretty">Switch tabs &mdash; the thumb glides, panels cross-fade. Radios give the group arrow-key switching natively.</p>
  </main>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tsl25-bg: #171210;
  --color-tsl25-bg: oklch(0.13 0.013 35);
  --color-tsl25-panel: #201a16;
  --color-tsl25-panel: oklch(0.17 0.016 35);
  --color-tsl25-well: #1a1512;
  --color-tsl25-well: oklch(0.14 0.014 35);
  --color-tsl25-ink: #f2ece7;
  --color-tsl25-ink: oklch(0.94 0.006 35);
  --color-tsl25-mut: #a3968c;
  --color-tsl25-mut: oklch(0.65 0.014 35);
  --color-tsl25-placeholder: #8a7d73;
  --color-tsl25-placeholder: oklch(0.57 0.014 35);
  --color-tsl25-line: #3d342e;
  --color-tsl25-line: oklch(0.28 0.018 35);
  --color-tsl25-accent: #e0904f;
  --color-tsl25-accent: oklch(0.72 0.15 40);
  --color-tsl25-thumb: rgb(224 144 79 / 0.22);
  --color-tsl25-thumb: color-mix(in oklab, oklch(0.72 0.15 40) 22%, transparent);
  --color-tsl25-thumbline: rgb(224 144 79 / 0.4);
  --color-tsl25-thumbline: color-mix(in oklab, oklch(0.72 0.15 40) 40%, transparent);
  --color-tsl25-ring: rgb(224 144 79 / 0.2);
  --color-tsl25-ring: color-mix(in oklab, oklch(0.72 0.15 40) 20%, transparent);
  --color-tsl25-hover: rgb(242 236 231 / 0.06);
  --color-tsl25-hover: color-mix(in oklab, oklch(0.94 0.006 35) 6%, transparent);
  --color-tsl25-tint: rgb(224 144 79 / 0.12);
  --color-tsl25-tint: color-mix(in oklab, oklch(0.72 0.15 40) 12%, transparent);
  --font-tsl25-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --animate-tsl25-in: tsl25-in .25s ease;
}

@keyframes tsl25-in {
  from {
    opacity: 0;
    translate: 0 4px;
  }
}

.tsl-25 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: dark;
}
/* STATE — thumb position, tab colour and which panel is displayed.
   display, not opacity: a hidden panel must leave the tab order. */

#tsl25-t2:checked ~ [role="group"] .tsl-25-thumb {
  translate: 100% 0;
}

#tsl25-t3:checked ~ [role="group"] .tsl-25-thumb {
  translate: 200% 0;
}

#tsl25-t1:checked ~ [role="group"] label[for="tsl25-t1"],
#tsl25-t2:checked ~ [role="group"] label[for="tsl25-t2"],
#tsl25-t3:checked ~ [role="group"] label[for="tsl25-t3"] {
  color: var(--color-tsl25-ink);
}

#tsl25-t1:checked ~ [data-panel="1"],
#tsl25-t2:checked ~ [data-panel="2"],
#tsl25-t3:checked ~ [data-panel="3"] {
  display: flex;
}
```

How this works

Radios are the correct primitive. A same-name group gives the browser's roving arrow-key protocol, which matches the WAI-ARIA tabs keyboard model without a line of script. The trade-off is honest and documented: full role="tablist" semantics still need a little JS.

hidden beats opacity-0. Display-switched panels drop out of the tab order automatically; an opacity-hidden panel leaves invisible focusable content behind, which is one of the most common tab-component bugs.

Make it yours

  • n tabs: thumb width w-[calc((100%-8px)/n)], stops at 0…(n&minus;1)×100%.
  • Icon-only tabs at narrow widths: hide the label text under a container query (demo 29).
  • Badge a tab with demo 14's dot.

Gotchas — read before shipping

  • Do not swap hidden for opacity-0 — invisible focusable content is a real accessibility failure, not a nitpick.
  • Keep the entry animation on the ACTIVE state, not the base class, or some engines replay it on unrelated repaints.
  • sr-only radios still need a visible focus indicator somewhere — here the strip takes it.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

:has() drives the panel switch; the sibling-combinator form works where radios precede panels in source.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Plain CSSno Tailwind surface

Search CodeFronts

Loading…