03 / 25
All 25 in 25 CSS Button Groups
- 01CSS Split Button with Anchor-Positioned Popover Menu (Popover API, No JS)
- 02CSS Segmented Control with Motion-Path Comet Thumb (offset-path Arc Travel)
- 03CSS View Transitions Toggle Button Group with Morphing Thumb (Shared-Element FLIP)
- 04CSS Pricing Toggle with Monthly / Yearly Switch (Pure CSS, No JavaScript)
- 05CSS Segmented Control with Sliding Thumb Indicator (iOS-Style, Pure CSS)
- 06CSS Split Button Dropdown with Native <details> (Primary Action + Menu)
- 07CSS Icon Button Group for Toolbar Actions (Accessible Tooltips, Pure CSS)
- 08CSS Pill Radio Button Group with Sliding Indicator (Filter Pills, Pure CSS)
- 09CSS Toggle Button Group with Radio Inputs (Single-Select Alignment Group)
- 10CSS Wallet Connect Button Grid for Web3 Sign-In (MetaMask, WalletConnect, Coinbase)
- 11CSS AI Model Selector Button Group with Provider Badges (LLM Model Picker)
- 12CSS Segmented Control with Liquid Mercury Thumb (Metallic Stretch Animation)
- 13CSS Button Group with Subgrid Underline Beam (Perfectly Aligned Tab Indicator)
- 14CSS Multi-Select Chip Buttons with Acid Glow (Toggleable Tag Filters)
- 15CSS Split Button with Quantum Chevron Reveal (Animated Menu Materialize)
- 16CSS Format Toggle Button Group with Conic Fill (Radial Sweep Select)
- 17CSS Pixel Matrix Button Group with LED Segment Glow (Retro Display Buttons)
- 18CSS Multi-Action Button Group with Gradient Flow Progress (Animated CTA Bar)
- 19CSS Icon Segmented Control with Holographic Sheen (Iridescent View Switcher)
- 20CSS Vertical Button Stack with 3D Hover Depth (Tilting Card Buttons)
- 21CSS Floating Action Button with Radial Sub-Button Menu (Arc Fan-Out, Pure CSS)
- 22CSS Filter Button Group with RGB-Split Glitch Active State (Cyberpunk Toggle)
- 23CSS Pill Button Group with Rotating Conic Plasma Border (Animated Gradient Ring)
- 24CSS Action Button Row with Radar Shockwave Click Feedback (Ripple Pulse)
- 25CSS Date-Range Preset Button Group with Cursor-Following Glow (Spotlight Hover)
CSS View Transitions Toggle Button Group with Morphing Thumb (Shared-Element FLIP)
A toggle button group where the active highlight doesn't slide — it morphs. Powered by the View Transitions API, the highlight is a shared element that the browser FLIP-animates from the old button to the new one, smoothly stretching across size and position changes. The forward-looking 'CSS View Transitions button group morphing highlight' pattern for 2027 UIs.
Build UI like this from scratch: Flexbox, Grid, animations & responsive CSS
ZTM · The CSS Bootcamp · 30‑day money-back guarantee · Affiliate link
The code
<section class="btn-03" aria-label="View transitions morphing toggle group demo">
<div class="btn-03__wrap">
<p class="btn-03__label">Workspace theme</p>
<div class="btn-03__group" id="btn-03-group" role="radiogroup" aria-label="Workspace theme">
<button type="button" class="btn-03__btn" role="radio" aria-checked="true" tabindex="0"><span class="btn-03__hl"></span><span class="btn-03__t">Focus</span></button>
<button type="button" class="btn-03__btn" role="radio" aria-checked="false" tabindex="-1"><span class="btn-03__hl"></span><span class="btn-03__t">Comfortable</span></button>
<button type="button" class="btn-03__btn" role="radio" aria-checked="false" tabindex="-1"><span class="btn-03__hl"></span><span class="btn-03__t">Compact</span></button>
<button type="button" class="btn-03__btn" role="radio" aria-checked="false" tabindex="-1"><span class="btn-03__hl"></span><span class="btn-03__t">Zen</span></button>
</div>
</div>
</section><section class="btn-03" aria-label="View transitions morphing toggle group demo">
<div class="btn-03__wrap">
<p class="btn-03__label">Workspace theme</p>
<div class="btn-03__group" id="btn-03-group" role="radiogroup" aria-label="Workspace theme">
<button type="button" class="btn-03__btn" role="radio" aria-checked="true" tabindex="0"><span class="btn-03__hl"></span><span class="btn-03__t">Focus</span></button>
<button type="button" class="btn-03__btn" role="radio" aria-checked="false" tabindex="-1"><span class="btn-03__hl"></span><span class="btn-03__t">Comfortable</span></button>
<button type="button" class="btn-03__btn" role="radio" aria-checked="false" tabindex="-1"><span class="btn-03__hl"></span><span class="btn-03__t">Compact</span></button>
<button type="button" class="btn-03__btn" role="radio" aria-checked="false" tabindex="-1"><span class="btn-03__hl"></span><span class="btn-03__t">Zen</span></button>
</div>
</div>
</section>.btn-03,
.btn-03 *,
.btn-03 *::before,
.btn-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.btn-03 {
--accent: oklch(0.5 0.22 265);
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(160deg,#f4f5fa,#e7e9f3);
padding: 44px 20px;
}
.btn-03__wrap {
width: min(560px,100%);
}
.btn-03__label {
font-size: 12px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--accent);
margin: 0 0 14px 4px;
}
.btn-03__group {
display: inline-flex;
gap: 6px;
padding: 6px;
background: #fff;
border: 1px solid #e2e4f0;
border-radius: 16px;
box-shadow: 0 16px 40px -26px rgba(40,30,120,.5);
}
.btn-03__btn {
position: relative;
border: none;
background: none;
min-height: 46px;
padding: 0 22px;
border-radius: 11px;
font-size: 15px;
font-weight: 700;
color: #5a5f80;
cursor: pointer;
overflow: hidden;
transition: color .2s;
}
.btn-03__hl {
position: absolute;
inset: 0;
border-radius: 11px;
background: var(--accent);
opacity: 0;
}
.btn-03__t {
position: relative;
z-index: 1;
}
.btn-03__btn[aria-checked=true] {
color: #fff;
}
.btn-03__btn[aria-checked=true] .btn-03__hl {
opacity: 1;
view-transition-name: btn-03-thumb;
box-shadow: 0 6px 16px -6px var(--accent);
}
.btn-03__btn:not([aria-checked=true]):hover {
color: #2a2e50;
}
.btn-03__btn:focus-visible {
outline: 3px solid var(--accent);
outline-offset: 2px;
}
::view-transition-group(btn-03-thumb) {
animation-duration: .45s;
animation-timing-function: cubic-bezier(.34,1.25,.4,1);
}
@media (prefers-reduced-motion: reduce) {
::view-transition-group(btn-03-thumb) {
animation-duration: 1ms;
}
}.btn-03,
.btn-03 *,
.btn-03 *::before,
.btn-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.btn-03 {
--accent: oklch(0.5 0.22 265);
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(160deg,#f4f5fa,#e7e9f3);
padding: 44px 20px;
}
.btn-03__wrap {
width: min(560px,100%);
}
.btn-03__label {
font-size: 12px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--accent);
margin: 0 0 14px 4px;
}
.btn-03__group {
display: inline-flex;
gap: 6px;
padding: 6px;
background: #fff;
border: 1px solid #e2e4f0;
border-radius: 16px;
box-shadow: 0 16px 40px -26px rgba(40,30,120,.5);
}
.btn-03__btn {
position: relative;
border: none;
background: none;
min-height: 46px;
padding: 0 22px;
border-radius: 11px;
font-size: 15px;
font-weight: 700;
color: #5a5f80;
cursor: pointer;
overflow: hidden;
transition: color .2s;
}
.btn-03__hl {
position: absolute;
inset: 0;
border-radius: 11px;
background: var(--accent);
opacity: 0;
}
.btn-03__t {
position: relative;
z-index: 1;
}
.btn-03__btn[aria-checked=true] {
color: #fff;
}
.btn-03__btn[aria-checked=true] .btn-03__hl {
opacity: 1;
view-transition-name: btn-03-thumb;
box-shadow: 0 6px 16px -6px var(--accent);
}
.btn-03__btn:not([aria-checked=true]):hover {
color: #2a2e50;
}
.btn-03__btn:focus-visible {
outline: 3px solid var(--accent);
outline-offset: 2px;
}
::view-transition-group(btn-03-thumb) {
animation-duration: .45s;
animation-timing-function: cubic-bezier(.34,1.25,.4,1);
}
@media (prefers-reduced-motion: reduce) {
::view-transition-group(btn-03-thumb) {
animation-duration: 1ms;
}
}Here's a working CSS Button Group 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: CSS View Transitions Toggle Button Group with Morphing Thumb (Shared-Element FLIP)
Source: https://codefronts.com/components/css-button-groups/css-view-transitions-toggle-button-group-with-morphing-thumb-shared-element-flip/
A toggle button group where the active highlight doesn't slide — it morphs. Powered by the View Transitions API, the highlight is a shared element that the browser FLIP-animates from the old button to the new one, smoothly stretching across size and position changes. The forward-looking 'CSS View Transitions button group morphing highlight' pattern for 2027 UIs.
## HTML
```html
<section class="btn-03" aria-label="View transitions morphing toggle group demo">
<div class="btn-03__wrap">
<p class="btn-03__label">Workspace theme</p>
<div class="btn-03__group" id="btn-03-group" role="radiogroup" aria-label="Workspace theme">
<button type="button" class="btn-03__btn" role="radio" aria-checked="true" tabindex="0"><span class="btn-03__hl"></span><span class="btn-03__t">Focus</span></button>
<button type="button" class="btn-03__btn" role="radio" aria-checked="false" tabindex="-1"><span class="btn-03__hl"></span><span class="btn-03__t">Comfortable</span></button>
<button type="button" class="btn-03__btn" role="radio" aria-checked="false" tabindex="-1"><span class="btn-03__hl"></span><span class="btn-03__t">Compact</span></button>
<button type="button" class="btn-03__btn" role="radio" aria-checked="false" tabindex="-1"><span class="btn-03__hl"></span><span class="btn-03__t">Zen</span></button>
</div>
</div>
</section>
```
## CSS
```css
.btn-03,
.btn-03 *,
.btn-03 *::before,
.btn-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.btn-03 {
--accent: oklch(0.5 0.22 265);
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(160deg,#f4f5fa,#e7e9f3);
padding: 44px 20px;
}
.btn-03__wrap {
width: min(560px,100%);
}
.btn-03__label {
font-size: 12px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--accent);
margin: 0 0 14px 4px;
}
.btn-03__group {
display: inline-flex;
gap: 6px;
padding: 6px;
background: #fff;
border: 1px solid #e2e4f0;
border-radius: 16px;
box-shadow: 0 16px 40px -26px rgba(40,30,120,.5);
}
.btn-03__btn {
position: relative;
border: none;
background: none;
min-height: 46px;
padding: 0 22px;
border-radius: 11px;
font-size: 15px;
font-weight: 700;
color: #5a5f80;
cursor: pointer;
overflow: hidden;
transition: color .2s;
}
.btn-03__hl {
position: absolute;
inset: 0;
border-radius: 11px;
background: var(--accent);
opacity: 0;
}
.btn-03__t {
position: relative;
z-index: 1;
}
.btn-03__btn[aria-checked=true] {
color: #fff;
}
.btn-03__btn[aria-checked=true] .btn-03__hl {
opacity: 1;
view-transition-name: btn-03-thumb;
box-shadow: 0 6px 16px -6px var(--accent);
}
.btn-03__btn:not([aria-checked=true]):hover {
color: #2a2e50;
}
.btn-03__btn:focus-visible {
outline: 3px solid var(--accent);
outline-offset: 2px;
}
::view-transition-group(btn-03-thumb) {
animation-duration: .45s;
animation-timing-function: cubic-bezier(.34,1.25,.4,1);
}
@media (prefers-reduced-motion: reduce) {
::view-transition-group(btn-03-thumb) {
animation-duration: 1ms;
}
}
```Here's a working CSS Button Group 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: CSS View Transitions Toggle Button Group with Morphing Thumb (Shared-Element FLIP)
Source: https://codefronts.com/components/css-button-groups/css-view-transitions-toggle-button-group-with-morphing-thumb-shared-element-flip/
A toggle button group where the active highlight doesn't slide — it morphs. Powered by the View Transitions API, the highlight is a shared element that the browser FLIP-animates from the old button to the new one, smoothly stretching across size and position changes. The forward-looking 'CSS View Transitions button group morphing highlight' pattern for 2027 UIs.
## HTML
```html
<section class="btn-03" aria-label="View transitions morphing toggle group demo">
<div class="btn-03__wrap">
<p class="btn-03__label">Workspace theme</p>
<div class="btn-03__group" id="btn-03-group" role="radiogroup" aria-label="Workspace theme">
<button type="button" class="btn-03__btn" role="radio" aria-checked="true" tabindex="0"><span class="btn-03__hl"></span><span class="btn-03__t">Focus</span></button>
<button type="button" class="btn-03__btn" role="radio" aria-checked="false" tabindex="-1"><span class="btn-03__hl"></span><span class="btn-03__t">Comfortable</span></button>
<button type="button" class="btn-03__btn" role="radio" aria-checked="false" tabindex="-1"><span class="btn-03__hl"></span><span class="btn-03__t">Compact</span></button>
<button type="button" class="btn-03__btn" role="radio" aria-checked="false" tabindex="-1"><span class="btn-03__hl"></span><span class="btn-03__t">Zen</span></button>
</div>
</div>
</section>
```
## CSS
```css
.btn-03,
.btn-03 *,
.btn-03 *::before,
.btn-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.btn-03 {
--accent: oklch(0.5 0.22 265);
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(160deg,#f4f5fa,#e7e9f3);
padding: 44px 20px;
}
.btn-03__wrap {
width: min(560px,100%);
}
.btn-03__label {
font-size: 12px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--accent);
margin: 0 0 14px 4px;
}
.btn-03__group {
display: inline-flex;
gap: 6px;
padding: 6px;
background: #fff;
border: 1px solid #e2e4f0;
border-radius: 16px;
box-shadow: 0 16px 40px -26px rgba(40,30,120,.5);
}
.btn-03__btn {
position: relative;
border: none;
background: none;
min-height: 46px;
padding: 0 22px;
border-radius: 11px;
font-size: 15px;
font-weight: 700;
color: #5a5f80;
cursor: pointer;
overflow: hidden;
transition: color .2s;
}
.btn-03__hl {
position: absolute;
inset: 0;
border-radius: 11px;
background: var(--accent);
opacity: 0;
}
.btn-03__t {
position: relative;
z-index: 1;
}
.btn-03__btn[aria-checked=true] {
color: #fff;
}
.btn-03__btn[aria-checked=true] .btn-03__hl {
opacity: 1;
view-transition-name: btn-03-thumb;
box-shadow: 0 6px 16px -6px var(--accent);
}
.btn-03__btn:not([aria-checked=true]):hover {
color: #2a2e50;
}
.btn-03__btn:focus-visible {
outline: 3px solid var(--accent);
outline-offset: 2px;
}
::view-transition-group(btn-03-thumb) {
animation-duration: .45s;
animation-timing-function: cubic-bezier(.34,1.25,.4,1);
}
@media (prefers-reduced-motion: reduce) {
::view-transition-group(btn-03-thumb) {
animation-duration: 1ms;
}
}
```How this works
Each button owns a highlight layer, but only the active one carries view-transition-name:btn-03-thumb. When selection changes, a ~20-line accessible radiogroup helper wraps the state update in document.startViewTransition(). The browser snapshots the old highlight and the new one, and because they share a transition name it treats them as the same element — morphing position AND size (a real shared-element transition, not a CSS translate).
The result reads like the highlight liquid-morphs between buttons of different widths, something a fixed-width sliding thumb can't do. Keyboard arrow keys and roving tabindex keep it a proper radiogroup; without the API it degrades to an instant, still-correct toggle.
.btn[aria-checked=true] .hl{ view-transition-name:btn-03-thumb }
document.startViewTransition(() => setActive(btn)); // browser FLIP-morphs the highlightTechniques used: View Transitions API shared-element morph (position + size FLIP) · single view-transition-name on the active highlight only · accessible radiogroup with roving tabindex + arrow keys · ::view-transition-group custom easing · reduced-motion instant fallback · progressive enhancement (works with no API)
Make it yours
- Morph feel = the
::view-transition-group(btn-03-thumb)animation duration/easing. - Highlight color =
--accent. - Add options freely — variable widths are exactly what the morph shows off.
- Swap to a pill or underline highlight; the shared-element morph adapts.
Gotchas — read before shipping
- A
view-transition-namemust be unique per snapshot — keep it ONLY on the active highlight, never on all of them. - The View Transitions API is Chrome/Safari; guard with
if (document.startViewTransition)and fall back to an instant update (done here). - Keep it a real radiogroup: roving tabindex + arrow keys, one Tab stop — don't lose keyboard operability for the effect.
- Neutralize the transition under
prefers-reduced-motionby zeroing the ::view-transition-group animation.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 18+ | no | 111+ |
Same-document View Transitions ship in Chrome 111+ and Safari 18+; Firefox not yet. It's a progressive enhancement — the group works everywhere and only gains the morph where supported.