32 Tailwind CSS Tabs
10 / 32
Pure TailwindMIT licensed

Expanding Icon Tabs in Tailwind CSS

Compact icon-only tabs in a white pill; the active one fills rose and its label morphs in beside the icon. The label animates max-w-0 → max-w-28 while the gap opens gap-0 → gap-2 on the same curve, so icon and text glide together. Inbox gains an unread badge.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="ttb-10 grid min-h-svh w-full place-items-center bg-ttb10-bg px-5 py-14 font-ttb10-sans text-ttb10-ink" aria-label="Expanding icon tabs demo">
  <div class="grid justify-items-center gap-8">
    <div role="radiogroup" aria-label="Main navigation" class="flex gap-1.5 rounded-full bg-white p-1.5 shadow-[0_6px_20px_-6px_color-mix(in_oklab,var(--color-ttb10-acc)_30%,transparent)] ring-1 ring-black/5">
      <label class="group/tab relative flex min-h-11 cursor-pointer items-center gap-0 rounded-full px-3.5 text-ttb10-mut transition-[background-color,color,gap] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] hover:text-ttb10-acc-ink has-checked:gap-2 has-checked:bg-ttb10-acc has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-3 has-focus-visible:outline-dashed has-focus-visible:outline-ttb10-acc motion-reduce:transition-none"><input class="sr-only" type="radio" name="ttb10" id="ttb10-r1" checked><svg aria-hidden="true" class="size-[18px] flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M3 11l9-8 9 8M5 9.5V20h5v-6h4v6h5V9.5"/></svg><span class="max-w-0 overflow-hidden text-[13px] font-bold whitespace-nowrap opacity-0 transition-[max-width,opacity] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked/tab:max-w-28 group-has-checked/tab:opacity-100 motion-reduce:transition-none">Home</span></label>
      <label class="group/tab relative flex min-h-11 cursor-pointer items-center gap-0 rounded-full px-3.5 text-ttb10-mut transition-[background-color,color,gap] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] hover:text-ttb10-acc-ink has-checked:gap-2 has-checked:bg-ttb10-acc has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-3 has-focus-visible:outline-dashed has-focus-visible:outline-ttb10-acc motion-reduce:transition-none"><input class="sr-only" type="radio" name="ttb10" id="ttb10-r2"><svg aria-hidden="true" class="size-[18px] flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5"/></svg><span class="max-w-0 overflow-hidden text-[13px] font-bold whitespace-nowrap opacity-0 transition-[max-width,opacity] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked/tab:max-w-28 group-has-checked/tab:opacity-100 motion-reduce:transition-none">Search</span></label>
      <label class="group/tab relative flex min-h-11 cursor-pointer items-center gap-0 rounded-full px-3.5 text-ttb10-mut transition-[background-color,color,gap] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] hover:text-ttb10-acc-ink has-checked:gap-2 has-checked:bg-ttb10-acc has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-3 has-focus-visible:outline-dashed has-focus-visible:outline-ttb10-acc motion-reduce:transition-none"><input class="sr-only" type="radio" name="ttb10" id="ttb10-r3"><svg aria-hidden="true" class="size-[18px] flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M3 13h5l1.5 3h5L16 13h5M5 4h14l2 9v6H3v-6z"/></svg><span class="max-w-0 overflow-hidden text-[13px] font-bold whitespace-nowrap opacity-0 transition-[max-width,opacity] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked/tab:max-w-28 group-has-checked/tab:opacity-100 motion-reduce:transition-none">Inbox</span><span class="absolute top-1.5 right-2 grid size-4 place-items-center rounded-full bg-ttb10-acc text-[9.5px] font-extrabold text-white ring-2 ring-white transition-transform group-has-checked/tab:scale-0"><span class="sr-only">unread: </span>4</span></label>
      <label class="group/tab relative flex min-h-11 cursor-pointer items-center gap-0 rounded-full px-3.5 text-ttb10-mut transition-[background-color,color,gap] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] hover:text-ttb10-acc-ink has-checked:gap-2 has-checked:bg-ttb10-acc has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-3 has-focus-visible:outline-dashed has-focus-visible:outline-ttb10-acc motion-reduce:transition-none"><input class="sr-only" type="radio" name="ttb10" id="ttb10-r4"><svg aria-hidden="true" class="size-[18px] flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><circle cx="12" cy="8" r="4"/><path d="M4 21v-1a7 7 0 0 1 7-7h2a7 7 0 0 1 7 7v1"/></svg><span class="max-w-0 overflow-hidden text-[13px] font-bold whitespace-nowrap opacity-0 transition-[max-width,opacity] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked/tab:max-w-28 group-has-checked/tab:opacity-100 motion-reduce:transition-none">Profile</span></label>
    </div>
    <p aria-hidden="true" class="rounded-full border border-black/8 bg-white/70 px-3.5 py-2 font-ttb10-mono text-[11.5px] text-ttb10-mut">max-w-0 → max-w-28 · gap-0 → gap-2 · same curve</p>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-ttb10-bg: #fbf3e8;
  --color-ttb10-bg: oklch(0.967 0.017 76.1);
  --color-ttb10-ink: #2a1d24;
  --color-ttb10-ink: oklch(0.250 0.024 345.4);
  --color-ttb10-mut: #74586a;
  --color-ttb10-mut: oklch(0.495 0.045 340.1);
  --color-ttb10-acc: #c93a76;
  --color-ttb10-acc: oklch(0.576 0.185 359.2);
  --color-ttb10-acc-ink: #a92c60;
  --color-ttb10-acc-ink: oklch(0.502 0.166 359.9);
  --font-ttb10-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --font-ttb10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
   stage is sized before the browser build compiles. */

.ttb-10 {
  width: 100%;
  min-height: 100svh;
}

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-ttb10-bg: #fbf3e8;
  --color-ttb10-bg: oklch(0.967 0.017 76.1);
  --color-ttb10-ink: #2a1d24;
  --color-ttb10-ink: oklch(0.250 0.024 345.4);
  --color-ttb10-mut: #74586a;
  --color-ttb10-mut: oklch(0.495 0.045 340.1);
  --color-ttb10-acc: #c93a76;
  --color-ttb10-acc: oklch(0.576 0.185 359.2);
  --color-ttb10-acc-ink: #a92c60;
  --color-ttb10-acc-ink: oklch(0.502 0.166 359.9);
  --font-ttb10-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --font-ttb10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
   stage is sized before the browser build compiles. */

.ttb-10 {
  width: 100%;
  min-height: 100svh;
}
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&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • .tt30l's max-width:0 → 120px reveal became max-w-0 group-has-checked/tab:max-w-28; the parent's gap transition kept the same cubic-bezier(0.4,0,0.2,1).
  • Icons became aria-hidden inline SVG with stroke="currentColor", so a single colour utility drives icon and label.
  • UI upgrade from the CSS original: an unread badge on Inbox that scales away when the tab opens, and the active pill is white-on-rose at 5.1:1 (the original #d4437f with white was 4.0:1).
  • UI upgrade: resting icons darkened from #94748a (3.6:1 on white) to a 6.4:1 token, and every tab is 44px tall.
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: Expanding Icon Tabs in Tailwind CSS
Source: https://codefronts.com/navigation/tailwind-css-tabs/expanding-icon-tabs-tailwind-css/

Compact icon-only tabs in a white pill; the active one fills rose and its label morphs in beside the icon. The label animates max-w-0 → max-w-28 while the gap opens gap-0 → gap-2 on the same curve, so icon and text glide together. Inbox gains an unread badge.
## HTML
```html
<section class="ttb-10 grid min-h-svh w-full place-items-center bg-ttb10-bg px-5 py-14 font-ttb10-sans text-ttb10-ink" aria-label="Expanding icon tabs demo">
  <div class="grid justify-items-center gap-8">
    <div role="radiogroup" aria-label="Main navigation" class="flex gap-1.5 rounded-full bg-white p-1.5 shadow-[0_6px_20px_-6px_color-mix(in_oklab,var(--color-ttb10-acc)_30%,transparent)] ring-1 ring-black/5">
      <label class="group/tab relative flex min-h-11 cursor-pointer items-center gap-0 rounded-full px-3.5 text-ttb10-mut transition-[background-color,color,gap] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] hover:text-ttb10-acc-ink has-checked:gap-2 has-checked:bg-ttb10-acc has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-3 has-focus-visible:outline-dashed has-focus-visible:outline-ttb10-acc motion-reduce:transition-none"><input class="sr-only" type="radio" name="ttb10" id="ttb10-r1" checked><svg aria-hidden="true" class="size-[18px] flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M3 11l9-8 9 8M5 9.5V20h5v-6h4v6h5V9.5"/></svg><span class="max-w-0 overflow-hidden text-[13px] font-bold whitespace-nowrap opacity-0 transition-[max-width,opacity] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked/tab:max-w-28 group-has-checked/tab:opacity-100 motion-reduce:transition-none">Home</span></label>
      <label class="group/tab relative flex min-h-11 cursor-pointer items-center gap-0 rounded-full px-3.5 text-ttb10-mut transition-[background-color,color,gap] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] hover:text-ttb10-acc-ink has-checked:gap-2 has-checked:bg-ttb10-acc has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-3 has-focus-visible:outline-dashed has-focus-visible:outline-ttb10-acc motion-reduce:transition-none"><input class="sr-only" type="radio" name="ttb10" id="ttb10-r2"><svg aria-hidden="true" class="size-[18px] flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5"/></svg><span class="max-w-0 overflow-hidden text-[13px] font-bold whitespace-nowrap opacity-0 transition-[max-width,opacity] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked/tab:max-w-28 group-has-checked/tab:opacity-100 motion-reduce:transition-none">Search</span></label>
      <label class="group/tab relative flex min-h-11 cursor-pointer items-center gap-0 rounded-full px-3.5 text-ttb10-mut transition-[background-color,color,gap] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] hover:text-ttb10-acc-ink has-checked:gap-2 has-checked:bg-ttb10-acc has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-3 has-focus-visible:outline-dashed has-focus-visible:outline-ttb10-acc motion-reduce:transition-none"><input class="sr-only" type="radio" name="ttb10" id="ttb10-r3"><svg aria-hidden="true" class="size-[18px] flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M3 13h5l1.5 3h5L16 13h5M5 4h14l2 9v6H3v-6z"/></svg><span class="max-w-0 overflow-hidden text-[13px] font-bold whitespace-nowrap opacity-0 transition-[max-width,opacity] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked/tab:max-w-28 group-has-checked/tab:opacity-100 motion-reduce:transition-none">Inbox</span><span class="absolute top-1.5 right-2 grid size-4 place-items-center rounded-full bg-ttb10-acc text-[9.5px] font-extrabold text-white ring-2 ring-white transition-transform group-has-checked/tab:scale-0"><span class="sr-only">unread: </span>4</span></label>
      <label class="group/tab relative flex min-h-11 cursor-pointer items-center gap-0 rounded-full px-3.5 text-ttb10-mut transition-[background-color,color,gap] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] hover:text-ttb10-acc-ink has-checked:gap-2 has-checked:bg-ttb10-acc has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-3 has-focus-visible:outline-dashed has-focus-visible:outline-ttb10-acc motion-reduce:transition-none"><input class="sr-only" type="radio" name="ttb10" id="ttb10-r4"><svg aria-hidden="true" class="size-[18px] flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><circle cx="12" cy="8" r="4"/><path d="M4 21v-1a7 7 0 0 1 7-7h2a7 7 0 0 1 7 7v1"/></svg><span class="max-w-0 overflow-hidden text-[13px] font-bold whitespace-nowrap opacity-0 transition-[max-width,opacity] duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked/tab:max-w-28 group-has-checked/tab:opacity-100 motion-reduce:transition-none">Profile</span></label>
    </div>
    <p aria-hidden="true" class="rounded-full border border-black/8 bg-white/70 px-3.5 py-2 font-ttb10-mono text-[11.5px] text-ttb10-mut">max-w-0 → max-w-28 · gap-0 → gap-2 · same curve</p>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-ttb10-bg: #fbf3e8;
  --color-ttb10-bg: oklch(0.967 0.017 76.1);
  --color-ttb10-ink: #2a1d24;
  --color-ttb10-ink: oklch(0.250 0.024 345.4);
  --color-ttb10-mut: #74586a;
  --color-ttb10-mut: oklch(0.495 0.045 340.1);
  --color-ttb10-acc: #c93a76;
  --color-ttb10-acc: oklch(0.576 0.185 359.2);
  --color-ttb10-acc-ink: #a92c60;
  --color-ttb10-acc-ink: oklch(0.502 0.166 359.9);
  --font-ttb10-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --font-ttb10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
   stage is sized before the browser build compiles. */

.ttb-10 {
  width: 100%;
  min-height: 100svh;
}
```

How this works

Animate a cap, not a width. width:auto can't transition, but max-width can — from 0 to a cap larger than the longest label:

<span class="max-w-0 overflow-hidden whitespace-nowrap opacity-0
  transition-[max-width,opacity] duration-400
  group-has-checked/tab:max-w-28 group-has-checked/tab:opacity-100">Inbox</span>

Accessible when collapsed. A zero-width label is still text in the tree, so every radio has a name for screen readers even while it shows only an icon. The CSS original had the same structure; the port keeps it deliberately.

Make it yours

  • Bottom bar: place the pill in a fixed footer; keep the radius.
  • Longer labels: raise max-w-28 past the widest string.
  • Swap the accent token; the shadow tint follows via color-mix().

Gotchas — read before shipping

  • max-width animation triggers layout every frame — fine for one bar, not for dozens animating at once.
  • If the cap is smaller than a label, the last letters clip silently.
  • Keep icons stroke="currentColor" so they change colour with the label.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Inside Tailwind v4's floor; no :has() panel here, only has-checked:.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…