25 Tailwind CSS Button Groups
03 / 25

All 25 in 25 Tailwind CSS Button Groups

  1. 01Tailwind Split Button with Anchor-Positioned Popover Dropdown (No JS)
  2. 02Tailwind Segmented Control with Motion-Path Comet Indicator
  3. 03Tailwind Toggle Button Group with View Transitions Morphing Highlight
  4. 04Tailwind Pricing Toggle — Monthly / Yearly Billing Switch (No JS)
  5. 05Tailwind Segmented Control with Sliding Thumb (iOS Style)
  6. 06Tailwind Split Button Dropdown with Native Details Menu (Save Options)
  7. 07Tailwind Icon Button Group Toolbar with Tooltips (Rich Text Editor)
  8. 08Tailwind Pill Filter Button Group with Sliding Indicator (Anchor Positioning)
  9. 09Tailwind Toggle Button Group for Text Alignment (Radio Inputs, Live Preview)
  10. 10Tailwind Web3 Connect Wallet Button Grid (Wallet Sign-In Modal)
  11. 11Tailwind AI Model Selector Button Group (LLM Model Picker with Badges)
  12. 12Tailwind Liquid Metal Segmented Control (Animated Chrome Thumb)
  13. 13Tailwind Tab Button Group with Subgrid Underline Indicator
  14. 14Tailwind Multi-Select Chip Buttons (Tag Filter Checkboxes with Count)
  15. 15Tailwind Animated Split Button Deploy Menu (Staggered Dropdown Reveal)
  16. 16Tailwind Radio Button Group with Conic Fill Animation (Export Format Picker)
  17. 17Tailwind Retro LED Pixel Button Group (Dot-Matrix Display Buttons)
  18. 18Tailwind Hold-to-Confirm Button Group with Gradient Progress (Press and Hold)
  19. 19Tailwind Holographic Icon Segmented Control (Grid / List View Switcher)
  20. 20Tailwind 3D Vertical Button Group with Hover Depth (Perspective Stack)
  21. 21Tailwind Floating Action Button with Radial Speed Dial Menu (No JS)
  22. 22Tailwind Glitch Effect Button Group (Cyberpunk RGB-Split Audio Filter)
  23. 23Tailwind Animated Gradient Border Pill Buttons (Rotating Conic Ring)
  24. 24Tailwind Button Group with Ripple Click Effect (Radar Shockwave Feedback)
  25. 25Tailwind Button Group with Cursor-Following Spotlight Glow (Date Range Presets)
Tailwind + JSMIT licensed

Tailwind Toggle Button Group with View Transitions Morphing Highlight

A density switcher whose highlight morphs from one button to the next with the View Transitions API — the browser snapshots the old and new highlight (both named [view-transition-name:tbg03-thumb]) and tweens between them, no position math. State is pure ARIA: aria-checked:text-white and group-aria-checked:opacity-100 read the attribute a 25-line controller sets, with full arrow-key support.

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="tbg-03 w-full min-h-svh grid place-items-center bg-tbg03-bg bg-linear-160 from-tbg03-bg to-tbg03-bg2 px-5 py-14 font-tbg03-sans text-tbg03-ink" aria-label="View transitions morphing toggle group demo">
  <div class="grid w-full max-w-xl gap-4">
    <p id="tbg03-label" class="ml-1 text-xs font-bold uppercase tracking-[0.16em] text-tbg03-acc">Workspace density</p>
    <div data-tbg03-group role="radiogroup" aria-labelledby="tbg03-label" class="inline-flex w-fit max-w-full flex-wrap gap-1.5 rounded-2xl border border-tbg03-line bg-white p-1.5 shadow-tbg03-lift">
      <button type="button" role="radio" aria-checked="true" tabindex="0" class="group relative inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-xl px-4.5 text-[15px] font-semibold text-tbg03-mut transition-colors hover:text-tbg03-ink aria-checked:text-white focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg03-acc"><span aria-hidden="true" class="absolute inset-0 rounded-xl bg-tbg03-acc opacity-0 shadow-[0_6px_16px_-6px_var(--color-tbg03-acc)] group-aria-checked:opacity-100 group-aria-checked:[view-transition-name:tbg03-thumb]"></span><svg class="relative" viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3"/></svg><span class="relative">Focus</span></button>
      <button type="button" role="radio" aria-checked="false" tabindex="-1" class="group relative inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-xl px-4.5 text-[15px] font-semibold text-tbg03-mut transition-colors hover:text-tbg03-ink aria-checked:text-white focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg03-acc"><span aria-hidden="true" class="absolute inset-0 rounded-xl bg-tbg03-acc opacity-0 shadow-[0_6px_16px_-6px_var(--color-tbg03-acc)] group-aria-checked:opacity-100 group-aria-checked:[view-transition-name:tbg03-thumb]"></span><svg class="relative" viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg><span class="relative">Comfortable</span></button>
      <button type="button" role="radio" aria-checked="false" tabindex="-1" class="group relative inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-xl px-4.5 text-[15px] font-semibold text-tbg03-mut transition-colors hover:text-tbg03-ink aria-checked:text-white focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg03-acc"><span aria-hidden="true" class="absolute inset-0 rounded-xl bg-tbg03-acc opacity-0 shadow-[0_6px_16px_-6px_var(--color-tbg03-acc)] group-aria-checked:opacity-100 group-aria-checked:[view-transition-name:tbg03-thumb]"></span><svg class="relative" viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M4 10h16M4 14h16M4 18h16"/></svg><span class="relative">Compact</span></button>
      <button type="button" role="radio" aria-checked="false" tabindex="-1" class="group relative inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-xl px-4.5 text-[15px] font-semibold text-tbg03-mut transition-colors hover:text-tbg03-ink aria-checked:text-white focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg03-acc"><span aria-hidden="true" class="absolute inset-0 rounded-xl bg-tbg03-acc opacity-0 shadow-[0_6px_16px_-6px_var(--color-tbg03-acc)] group-aria-checked:opacity-100 group-aria-checked:[view-transition-name:tbg03-thumb]"></span><svg class="relative" viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 4a8 8 0 1 0 8 8 6 6 0 0 1-8-8z"/></svg><span class="relative">Zen</span></button>
    </div>
    <p class="ml-1 text-[13px] text-tbg03-mut">Use the arrow keys to move between options.</p>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tbg03-bg: #f4f5fa;
  --color-tbg03-bg2: #e7e9f3;
  --color-tbg03-line: #e2e4f0;
  --color-tbg03-ink: #1e2140;
  --color-tbg03-mut: #575c7d;
  --color-tbg03-mut: oklch(0.47 0.05 275);
  --color-tbg03-acc: #3e3fd8;
  --color-tbg03-acc: oklch(0.5 0.22 270);
  --shadow-tbg03-lift: 0 16px 40px -26px rgb(40 30 120 / 0.5);
  --font-tbg03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
/* View-transition pseudo-elements live on the document root and have no Tailwind variant. */

::view-transition-group(tbg03-thumb) {
  animation-duration: 0.45s;
  animation-timing-function: cubic-bezier(0.34, 1.25, 0.4, 1);
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(tbg03-thumb) {
    animation-duration: 1ms;
  }
}
/* Root rule: the full-bleed contract, restated so the stage is sized before the browser build compiles. */

.tbg-03 {
  width: 100%;
  min-height: 100svh;
}
(() => {
  const reduce = matchMedia('(prefers-reduced-motion: reduce)');
  document.querySelectorAll('[data-tbg03-group]').forEach((group) => {
    if (group.dataset.wired) return;
    group.dataset.wired = '1';
    const items = [...group.querySelectorAll('[role="radio"]')];
    const select = (target, focus) => {
      const apply = () => items.forEach((b) => {
        const on = b === target;
        b.setAttribute('aria-checked', String(on));
        b.tabIndex = on ? 0 : -1;
      });
      if (document.startViewTransition && !reduce.matches) document.startViewTransition(apply);
      else apply();
      if (focus) target.focus();
    };
    items.forEach((b, i) => {
      b.addEventListener('click', () => select(b));
      b.addEventListener('keydown', (e) => {
        const step = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[e.key];
        if (!step) return;
        e.preventDefault();
        select(items[(i + step + items.length) % items.length], true);
      });
    });
  });
})();

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-tbg03-bg: #f4f5fa;
  --color-tbg03-bg2: #e7e9f3;
  --color-tbg03-line: #e2e4f0;
  --color-tbg03-ink: #1e2140;
  --color-tbg03-mut: #575c7d;
  --color-tbg03-mut: oklch(0.47 0.05 275);
  --color-tbg03-acc: #3e3fd8;
  --color-tbg03-acc: oklch(0.5 0.22 270);
  --shadow-tbg03-lift: 0 16px 40px -26px rgb(40 30 120 / 0.5);
  --font-tbg03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
/* View-transition pseudo-elements live on the document root and have no Tailwind variant. */

::view-transition-group(tbg03-thumb) {
  animation-duration: 0.45s;
  animation-timing-function: cubic-bezier(0.34, 1.25, 0.4, 1);
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(tbg03-thumb) {
    animation-duration: 1ms;
  }
}
/* Root rule: the full-bleed contract, restated so the stage is sized before the browser build compiles. */

.tbg-03 {
  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..800&display=swap">

What changed in the port

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

  • [aria-checked=true] selectors became the built-in aria-checked: and group-aria-checked: variants.
  • Bug fix: the CSS original declared a view transition but shipped no script to trigger it, so the buttons never changed state. The port adds the controller: click, arrow keys, roving tabindex.
  • UI upgrade: each option gained an icon that hints at its density, and the group wraps instead of overflowing on narrow screens.
  • UI upgrade: muted copy rose to oklch(0.47) (6:1 on white), and reduced-motion users get an instant switch.
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 Toggle Button Group with View Transitions Morphing Highlight
Source: https://codefronts.com/components/tailwind-css-button-groups/tailwind-toggle-button-group-view-transitions/

A density switcher whose highlight morphs from one button to the next with the View Transitions API — the browser snapshots the old and new highlight (both named [view-transition-name:tbg03-thumb]) and tweens between them, no position math. State is pure ARIA: aria-checked:text-white and group-aria-checked:opacity-100 read the attribute a 25-line controller sets, with full arrow-key support.
## HTML
```html
<section class="tbg-03 w-full min-h-svh grid place-items-center bg-tbg03-bg bg-linear-160 from-tbg03-bg to-tbg03-bg2 px-5 py-14 font-tbg03-sans text-tbg03-ink" aria-label="View transitions morphing toggle group demo">
  <div class="grid w-full max-w-xl gap-4">
    <p id="tbg03-label" class="ml-1 text-xs font-bold uppercase tracking-[0.16em] text-tbg03-acc">Workspace density</p>
    <div data-tbg03-group role="radiogroup" aria-labelledby="tbg03-label" class="inline-flex w-fit max-w-full flex-wrap gap-1.5 rounded-2xl border border-tbg03-line bg-white p-1.5 shadow-tbg03-lift">
      <button type="button" role="radio" aria-checked="true" tabindex="0" class="group relative inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-xl px-4.5 text-[15px] font-semibold text-tbg03-mut transition-colors hover:text-tbg03-ink aria-checked:text-white focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg03-acc"><span aria-hidden="true" class="absolute inset-0 rounded-xl bg-tbg03-acc opacity-0 shadow-[0_6px_16px_-6px_var(--color-tbg03-acc)] group-aria-checked:opacity-100 group-aria-checked:[view-transition-name:tbg03-thumb]"></span><svg class="relative" viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3"/></svg><span class="relative">Focus</span></button>
      <button type="button" role="radio" aria-checked="false" tabindex="-1" class="group relative inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-xl px-4.5 text-[15px] font-semibold text-tbg03-mut transition-colors hover:text-tbg03-ink aria-checked:text-white focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg03-acc"><span aria-hidden="true" class="absolute inset-0 rounded-xl bg-tbg03-acc opacity-0 shadow-[0_6px_16px_-6px_var(--color-tbg03-acc)] group-aria-checked:opacity-100 group-aria-checked:[view-transition-name:tbg03-thumb]"></span><svg class="relative" viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg><span class="relative">Comfortable</span></button>
      <button type="button" role="radio" aria-checked="false" tabindex="-1" class="group relative inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-xl px-4.5 text-[15px] font-semibold text-tbg03-mut transition-colors hover:text-tbg03-ink aria-checked:text-white focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg03-acc"><span aria-hidden="true" class="absolute inset-0 rounded-xl bg-tbg03-acc opacity-0 shadow-[0_6px_16px_-6px_var(--color-tbg03-acc)] group-aria-checked:opacity-100 group-aria-checked:[view-transition-name:tbg03-thumb]"></span><svg class="relative" viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M4 10h16M4 14h16M4 18h16"/></svg><span class="relative">Compact</span></button>
      <button type="button" role="radio" aria-checked="false" tabindex="-1" class="group relative inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-xl px-4.5 text-[15px] font-semibold text-tbg03-mut transition-colors hover:text-tbg03-ink aria-checked:text-white focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg03-acc"><span aria-hidden="true" class="absolute inset-0 rounded-xl bg-tbg03-acc opacity-0 shadow-[0_6px_16px_-6px_var(--color-tbg03-acc)] group-aria-checked:opacity-100 group-aria-checked:[view-transition-name:tbg03-thumb]"></span><svg class="relative" viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 4a8 8 0 1 0 8 8 6 6 0 0 1-8-8z"/></svg><span class="relative">Zen</span></button>
    </div>
    <p class="ml-1 text-[13px] text-tbg03-mut">Use the arrow keys to move between options.</p>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tbg03-bg: #f4f5fa;
  --color-tbg03-bg2: #e7e9f3;
  --color-tbg03-line: #e2e4f0;
  --color-tbg03-ink: #1e2140;
  --color-tbg03-mut: #575c7d;
  --color-tbg03-mut: oklch(0.47 0.05 275);
  --color-tbg03-acc: #3e3fd8;
  --color-tbg03-acc: oklch(0.5 0.22 270);
  --shadow-tbg03-lift: 0 16px 40px -26px rgb(40 30 120 / 0.5);
  --font-tbg03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
/* View-transition pseudo-elements live on the document root and have no Tailwind variant. */

::view-transition-group(tbg03-thumb) {
  animation-duration: 0.45s;
  animation-timing-function: cubic-bezier(0.34, 1.25, 0.4, 1);
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(tbg03-thumb) {
    animation-duration: 1ms;
  }
}
/* Root rule: the full-bleed contract, restated so the stage is sized before the browser build compiles. */

.tbg-03 {
  width: 100%;
  min-height: 100svh;
}
```

## JavaScript
```js
(() => {
  const reduce = matchMedia('(prefers-reduced-motion: reduce)');
  document.querySelectorAll('[data-tbg03-group]').forEach((group) => {
    if (group.dataset.wired) return;
    group.dataset.wired = '1';
    const items = [...group.querySelectorAll('[role="radio"]')];
    const select = (target, focus) => {
      const apply = () => items.forEach((b) => {
        const on = b === target;
        b.setAttribute('aria-checked', String(on));
        b.tabIndex = on ? 0 : -1;
      });
      if (document.startViewTransition && !reduce.matches) document.startViewTransition(apply);
      else apply();
      if (focus) target.focus();
    };
    items.forEach((b, i) => {
      b.addEventListener('click', () => select(b));
      b.addEventListener('keydown', (e) => {
        const step = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[e.key];
        if (!step) return;
        e.preventDefault();
        select(items[(i + step + items.length) % items.length], true);
      });
    });
  });
})();
```

How this works

One name, two elements, one morph. Every button has a highlight span, but only the checked one gets the transition name:

<span class="opacity-0 group-aria-checked:opacity-100
  group-aria-checked:[view-transition-name:tbg03-thumb]">

When the script flips aria-checked inside startViewTransition(), the browser sees tbg03-thumb move from one box to another and animates the size and position itself.

What stays CSS. ::view-transition-group(tbg03-thumb) is a pseudo-element on the document root with no Tailwind variant, so its timing curve lives in the stylesheet.

Make it yours

  • Springier morph: raise the second control point in the cubic-bezier inside the stylesheet.
  • Change options: add buttons with the same class list; the controller reads them all.
  • Re-theme via --color-tbg03-acc; text, highlight and focus ring follow.

Gotchas — read before shipping

  • A view-transition-name must be unique on the page at any moment — only ever apply it to the checked item.
  • Same-document view transitions are not in older Firefox; the controller falls back to an instant switch.
  • Users with reduced motion skip the transition entirely (checked in script and CSS).

Browser support

ChromeSafariFirefoxEdge
111+18+144+111+

Same-document View Transitions are Baseline 2025. Elsewhere the group still works — it just switches without morphing.

Techniques used in this demo

Search CodeFronts

Loading…