25 CSS Button Groups
21 / 25

All 25 in 25 CSS Button Groups

  1. 01CSS Split Button with Anchor-Positioned Popover Menu (Popover API, No JS)
  2. 02CSS Segmented Control with Motion-Path Comet Thumb (offset-path Arc Travel)
  3. 03CSS View Transitions Toggle Button Group with Morphing Thumb (Shared-Element FLIP)
  4. 04CSS Pricing Toggle with Monthly / Yearly Switch (Pure CSS, No JavaScript)
  5. 05CSS Segmented Control with Sliding Thumb Indicator (iOS-Style, Pure CSS)
  6. 06CSS Split Button Dropdown with Native <details> (Primary Action + Menu)
  7. 07CSS Icon Button Group for Toolbar Actions (Accessible Tooltips, Pure CSS)
  8. 08CSS Pill Radio Button Group with Sliding Indicator (Filter Pills, Pure CSS)
  9. 09CSS Toggle Button Group with Radio Inputs (Single-Select Alignment Group)
  10. 10CSS Wallet Connect Button Grid for Web3 Sign-In (MetaMask, WalletConnect, Coinbase)
  11. 11CSS AI Model Selector Button Group with Provider Badges (LLM Model Picker)
  12. 12CSS Segmented Control with Liquid Mercury Thumb (Metallic Stretch Animation)
  13. 13CSS Button Group with Subgrid Underline Beam (Perfectly Aligned Tab Indicator)
  14. 14CSS Multi-Select Chip Buttons with Acid Glow (Toggleable Tag Filters)
  15. 15CSS Split Button with Quantum Chevron Reveal (Animated Menu Materialize)
  16. 16CSS Format Toggle Button Group with Conic Fill (Radial Sweep Select)
  17. 17CSS Pixel Matrix Button Group with LED Segment Glow (Retro Display Buttons)
  18. 18CSS Multi-Action Button Group with Gradient Flow Progress (Animated CTA Bar)
  19. 19CSS Icon Segmented Control with Holographic Sheen (Iridescent View Switcher)
  20. 20CSS Vertical Button Stack with 3D Hover Depth (Tilting Card Buttons)
  21. 21CSS Floating Action Button with Radial Sub-Button Menu (Arc Fan-Out, Pure CSS)
  22. 22CSS Filter Button Group with RGB-Split Glitch Active State (Cyberpunk Toggle)
  23. 23CSS Pill Button Group with Rotating Conic Plasma Border (Animated Gradient Ring)
  24. 24CSS Action Button Row with Radar Shockwave Click Feedback (Ripple Pulse)
  25. 25CSS Date-Range Preset Button Group with Cursor-Following Glow (Spotlight Hover)
Pure CSSMIT licensed

CSS Floating Action Button with Radial Sub-Button Menu (Arc Fan-Out, Pure CSS)

A floating action button that fans a ring of sub-actions out along an arc when tapped — each satellite springs to its own orbital position on a staggered delay, and the FAB icon rotates into a close cross. The delightful 'CSS floating action button radial menu' (speed-dial) built with a checkbox toggle, no JavaScript.

Live Demo
Plain CSSTailwind v4
Try it

Build UI like this from scratch: Flexbox, Grid, animations & responsive CSS

ZTM · The CSS Bootcamp · 30‑day money-back guarantee · Affiliate link

10% off at checkout with code
Check ZTM Course

The code

<section class="btn-21" aria-label="Orbital floating action button demo">
  <div class="btn-21__fab" role="group" aria-label="Quick create">
    <button type="button" class="btn-21__sat" style="--x:0px;--y:-96px" aria-label="New note"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M4 4h12l4 4v12H4zM16 4v4h4"/></svg></button>
    <button type="button" class="btn-21__sat" style="--x:-84px;--y:-48px" aria-label="New task"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M4 12l5 5L20 6"/></svg></button>
    <button type="button" class="btn-21__sat" style="--x:-96px;--y:44px" aria-label="Upload"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M12 16V5M7 10l5-5 5 5M5 19h14"/></svg></button>
    <button type="button" class="btn-21__sat" style="--x:-40px;--y:88px" aria-label="Invite"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="9" cy="8" r="3.2"/><path d="M3.5 20a5.5 5.5 0 0 1 11 0M17 8v6M14 11h6"/></svg></button>
    <input type="checkbox" id="btn-21-t" class="btn-21__in">
    <label for="btn-21-t" class="btn-21__main" aria-label="Open quick create menu"><span class="btn-21__plus"></span></label>
  </div>
</section>
.btn-21,
.btn-21 *,
.btn-21 *::before,
.btn-21 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.btn-21 {
  --accent: oklch(0.6 0.24 300);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(120% 120% at 70% 70%,#221338,#0c0716);
  padding: 44px 20px;
}

.btn-21__fab {
  position: relative;
  width: 64px;
  height: 64px;
}

.btn-21__in {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.btn-21__main {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  box-shadow: 0 12px 30px -8px var(--accent),0 0 0 0 rgba(255,255,255,.2);
  z-index: 3;
  transition: transform .3s,box-shadow .3s;
}

.btn-21__main:hover {
  transform: scale(1.05);
}

.btn-21__plus {
  position: relative;
  width: 22px;
  height: 22px;
}

.btn-21__plus::before,
.btn-21__plus::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  background: #fff;
  border-radius: 2px;
  translate: -50% -50%;
  transition: transform .35s cubic-bezier(.34,1.4,.4,1);
}

.btn-21__plus::before {
  width: 22px;
  height: 3px;
}

.btn-21__plus::after {
  width: 3px;
  height: 22px;
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__plus::before {
  transform: rotate(135deg);
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__plus::after {
  transform: rotate(135deg);
}

.btn-21__sat {
  position: absolute;
  left: 11px;
  top: 11px;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: #2a1f45;
  color: #e6d8ff;
  cursor: pointer;
  z-index: 2;
  scale: 0;
  translate: 0 0;
  box-shadow: 0 8px 20px -8px #000;
  transition: translate .4s cubic-bezier(.34,1.5,.5,1),scale .35s cubic-bezier(.34,1.5,.5,1),background .18s;
}

.btn-21__sat:hover {
  background: var(--accent);
  color: #fff;
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__sat {
  translate: var(--x) var(--y);
  scale: 1;
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__sat:nth-child(1) {
  transition-delay: .02s;
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__sat:nth-child(2) {
  transition-delay: .07s;
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__sat:nth-child(3) {
  transition-delay: .12s;
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__sat:nth-child(4) {
  transition-delay: .17s;
}

.btn-21__in:focus-visible + .btn-21__main {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

.btn-21__sat:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .btn-21__sat,
    .btn-21__plus::before,
    .btn-21__plus::after {
    transition: scale .2s,translate .2s;
  }

  .btn-21__fab:has(.btn-21__in:checked) .btn-21__sat {
    transition-delay: 0s;
  }
}
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 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 Floating Action Button with Radial Sub-Button Menu (Arc Fan-Out, Pure CSS)
Source: https://codefronts.com/components/css-button-groups/orbital-fab/

A floating action button that fans a ring of sub-actions out along an arc when tapped — each satellite springs to its own orbital position on a staggered delay, and the FAB icon rotates into a close cross. The delightful 'CSS floating action button radial menu' (speed-dial) built with a checkbox toggle, no JavaScript.
## HTML
```html
<section class="btn-21" aria-label="Orbital floating action button demo">
  <div class="btn-21__fab" role="group" aria-label="Quick create">
    <button type="button" class="btn-21__sat" style="--x:0px;--y:-96px" aria-label="New note"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M4 4h12l4 4v12H4zM16 4v4h4"/></svg></button>
    <button type="button" class="btn-21__sat" style="--x:-84px;--y:-48px" aria-label="New task"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M4 12l5 5L20 6"/></svg></button>
    <button type="button" class="btn-21__sat" style="--x:-96px;--y:44px" aria-label="Upload"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M12 16V5M7 10l5-5 5 5M5 19h14"/></svg></button>
    <button type="button" class="btn-21__sat" style="--x:-40px;--y:88px" aria-label="Invite"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="9" cy="8" r="3.2"/><path d="M3.5 20a5.5 5.5 0 0 1 11 0M17 8v6M14 11h6"/></svg></button>
    <input type="checkbox" id="btn-21-t" class="btn-21__in">
    <label for="btn-21-t" class="btn-21__main" aria-label="Open quick create menu"><span class="btn-21__plus"></span></label>
  </div>
</section>
```
## CSS
```css
.btn-21,
.btn-21 *,
.btn-21 *::before,
.btn-21 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.btn-21 {
  --accent: oklch(0.6 0.24 300);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(120% 120% at 70% 70%,#221338,#0c0716);
  padding: 44px 20px;
}

.btn-21__fab {
  position: relative;
  width: 64px;
  height: 64px;
}

.btn-21__in {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.btn-21__main {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  box-shadow: 0 12px 30px -8px var(--accent),0 0 0 0 rgba(255,255,255,.2);
  z-index: 3;
  transition: transform .3s,box-shadow .3s;
}

.btn-21__main:hover {
  transform: scale(1.05);
}

.btn-21__plus {
  position: relative;
  width: 22px;
  height: 22px;
}

.btn-21__plus::before,
.btn-21__plus::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  background: #fff;
  border-radius: 2px;
  translate: -50% -50%;
  transition: transform .35s cubic-bezier(.34,1.4,.4,1);
}

.btn-21__plus::before {
  width: 22px;
  height: 3px;
}

.btn-21__plus::after {
  width: 3px;
  height: 22px;
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__plus::before {
  transform: rotate(135deg);
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__plus::after {
  transform: rotate(135deg);
}

.btn-21__sat {
  position: absolute;
  left: 11px;
  top: 11px;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: #2a1f45;
  color: #e6d8ff;
  cursor: pointer;
  z-index: 2;
  scale: 0;
  translate: 0 0;
  box-shadow: 0 8px 20px -8px #000;
  transition: translate .4s cubic-bezier(.34,1.5,.5,1),scale .35s cubic-bezier(.34,1.5,.5,1),background .18s;
}

.btn-21__sat:hover {
  background: var(--accent);
  color: #fff;
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__sat {
  translate: var(--x) var(--y);
  scale: 1;
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__sat:nth-child(1) {
  transition-delay: .02s;
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__sat:nth-child(2) {
  transition-delay: .07s;
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__sat:nth-child(3) {
  transition-delay: .12s;
}

.btn-21__fab:has(.btn-21__in:checked) .btn-21__sat:nth-child(4) {
  transition-delay: .17s;
}

.btn-21__in:focus-visible + .btn-21__main {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

.btn-21__sat:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .btn-21__sat,
    .btn-21__plus::before,
    .btn-21__plus::after {
    transition: scale .2s,translate .2s;
  }

  .btn-21__fab:has(.btn-21__in:checked) .btn-21__sat {
    transition-delay: 0s;
  }
}
```

How this works

A hidden <input type=checkbox> toggles the open state; :has(:checked) on the container animates everything. Each satellite button is absolutely centered on the FAB and, when open, translated to its orbital slot with a per-item --a angle and radius — computed once with calc() using cos/sin via paired X/Y offsets, and released on a transition-delay stagger so they bloom outward in sequence.

Closed, the satellites collapse back under the FAB at scale(0); the main + icon rotates 135° into an ×. A springy cubic-bezier gives each satellite an overshoot as it snaps to orbit — the signature speed-dial feel.

.container:has(:checked) .sat{ translate:var(--x) var(--y); scale:1 }
.sat:nth-child(2){ transition-delay:.04s } /* stagger */

Techniques used: checkbox + :has() open state · per-satellite polar offset (--x/--y) placement · staggered transition-delay fan-out · springy overshoot cubic-bezier · icon rotate to close cross · labels on hover

Make it yours

  • Orbit radius = the --x/--y offset magnitudes per satellite.
  • Arc spread: change each satellite's target angle/offsets.
  • Stagger feel = the per-child transition-delay step.
  • --accent tints the FAB and its glow.

Gotchas — read before shipping

  • The FAB toggle is a checkbox — for a true menu, add aria-expanded/role via a few lines of JS; for a demo the checkbox semantics are acceptable.
  • Each satellite needs a real accessible name (aria-label), not just an icon.
  • Keep satellites ≥44px and clear of screen edges when placed in a real corner.
  • Honor prefers-reduced-motion: drop the stagger and overshoot, keep a simple fade.

Browser support

ChromeSafariFirefoxEdge
111+15.4+121+111+

Floor set by :has(), oklch() as this demo is written. The core technique itself goes back further — Chrome 105+.

:has() Baseline 2023. Without it, wire the open class with ~5 lines of JS; the polar layout and stagger are plain transitions.

Techniques used in this demo

Search CodeFronts

Loading…