25 CSS Button Groups
05 / 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 Segmented Control with Sliding Thumb Indicator (iOS-Style, Pure CSS)

The iOS/macOS segmented control, rebuilt in pure CSS: three or four options with one animated thumb that glides beneath the selected label. The go-to 'CSS segmented control sliding thumb indicator' component for view switchers, filters and settings — accessible radio semantics, arrow-key support, and a thumb that never breaks alignment because it's sized by the grid itself.

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-05" aria-label="Segmented control sliding thumb demo">
  <div class="btn-05__wrap">
    <p class="btn-05__label">Timeframe</p>
    <div class="btn-05__seg" role="group" aria-label="Chart timeframe">
      <input type="radio" name="btn-05" id="btn-05-1" class="btn-05__in" checked>
      <label for="btn-05-1" class="btn-05__opt">Day</label>
      <input type="radio" name="btn-05" id="btn-05-2" class="btn-05__in">
      <label for="btn-05-2" class="btn-05__opt">Week</label>
      <input type="radio" name="btn-05" id="btn-05-3" class="btn-05__in">
      <label for="btn-05-3" class="btn-05__opt">Month</label>
      <input type="radio" name="btn-05" id="btn-05-4" class="btn-05__in">
      <label for="btn-05-4" class="btn-05__opt">Year</label>
      <span class="btn-05__thumb" aria-hidden="true"></span>
    </div>
  </div>
</section>
.btn-05,
.btn-05 *,
.btn-05 *::before,
.btn-05 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.btn-05 {
  --accent: oklch(0.55 0.2 285);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg,#f5f2ff,#efeaff 60%,#e7dfff);
  padding: 44px 20px;
}

.btn-05__wrap {
  width: min(520px,100%);
}

.btn-05__label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 12px 4px;
}

.btn-05__seg {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4,1fr);
  padding: 5px;
  background: #fff;
  border: 1px solid #e6def9;
  border-radius: 16px;
  box-shadow: 0 18px 44px -30px rgba(60,20,120,.6);
}

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

.btn-05__opt {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  font-size: 15px;
  font-weight: 650;
  color: #5b4b86;
  cursor: pointer;
  transition: color .25s;
  border-radius: 11px;
}

.btn-05__thumb {
  position: absolute;
  z-index: 0;
  top: 5px;
  left: 5px;
  width: calc((100% - 10px)/4);
  height: calc(100% - 10px);
  background: var(--accent);
  border-radius: 11px;
  box-shadow: 0 6px 16px -6px var(--accent);
  transition: translate .4s cubic-bezier(.34,1.4,.5,1);
}

.btn-05__seg:has(#btn-05-2:checked) .btn-05__thumb {
  translate: 100% 0;
}

.btn-05__seg:has(#btn-05-3:checked) .btn-05__thumb {
  translate: 200% 0;
}

.btn-05__seg:has(#btn-05-4:checked) .btn-05__thumb {
  translate: 300% 0;
}

.btn-05__in:checked + .btn-05__opt {
  color: #fff;
}

.btn-05__in:focus-visible + .btn-05__opt {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .btn-05__thumb,
    .btn-05__opt {
    transition: none;
  }
}
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 Segmented Control with Sliding Thumb Indicator (iOS-Style, Pure CSS)
Source: https://codefronts.com/components/css-button-groups/css-segmented-control-with-sliding-thumb-indicator-ios-style-pure-css/

The iOS/macOS segmented control, rebuilt in pure CSS: three or four options with one animated thumb that glides beneath the selected label. The go-to 'CSS segmented control sliding thumb indicator' component for view switchers, filters and settings — accessible radio semantics, arrow-key support, and a thumb that never breaks alignment because it's sized by the grid itself.
## HTML
```html
<section class="btn-05" aria-label="Segmented control sliding thumb demo">
  <div class="btn-05__wrap">
    <p class="btn-05__label">Timeframe</p>
    <div class="btn-05__seg" role="group" aria-label="Chart timeframe">
      <input type="radio" name="btn-05" id="btn-05-1" class="btn-05__in" checked>
      <label for="btn-05-1" class="btn-05__opt">Day</label>
      <input type="radio" name="btn-05" id="btn-05-2" class="btn-05__in">
      <label for="btn-05-2" class="btn-05__opt">Week</label>
      <input type="radio" name="btn-05" id="btn-05-3" class="btn-05__in">
      <label for="btn-05-3" class="btn-05__opt">Month</label>
      <input type="radio" name="btn-05" id="btn-05-4" class="btn-05__in">
      <label for="btn-05-4" class="btn-05__opt">Year</label>
      <span class="btn-05__thumb" aria-hidden="true"></span>
    </div>
  </div>
</section>
```
## CSS
```css
.btn-05,
.btn-05 *,
.btn-05 *::before,
.btn-05 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.btn-05 {
  --accent: oklch(0.55 0.2 285);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg,#f5f2ff,#efeaff 60%,#e7dfff);
  padding: 44px 20px;
}

.btn-05__wrap {
  width: min(520px,100%);
}

.btn-05__label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 12px 4px;
}

.btn-05__seg {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4,1fr);
  padding: 5px;
  background: #fff;
  border: 1px solid #e6def9;
  border-radius: 16px;
  box-shadow: 0 18px 44px -30px rgba(60,20,120,.6);
}

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

.btn-05__opt {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  font-size: 15px;
  font-weight: 650;
  color: #5b4b86;
  cursor: pointer;
  transition: color .25s;
  border-radius: 11px;
}

.btn-05__thumb {
  position: absolute;
  z-index: 0;
  top: 5px;
  left: 5px;
  width: calc((100% - 10px)/4);
  height: calc(100% - 10px);
  background: var(--accent);
  border-radius: 11px;
  box-shadow: 0 6px 16px -6px var(--accent);
  transition: translate .4s cubic-bezier(.34,1.4,.5,1);
}

.btn-05__seg:has(#btn-05-2:checked) .btn-05__thumb {
  translate: 100% 0;
}

.btn-05__seg:has(#btn-05-3:checked) .btn-05__thumb {
  translate: 200% 0;
}

.btn-05__seg:has(#btn-05-4:checked) .btn-05__thumb {
  translate: 300% 0;
}

.btn-05__in:checked + .btn-05__opt {
  color: #fff;
}

.btn-05__in:focus-visible + .btn-05__opt {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .btn-05__thumb,
    .btn-05__opt {
    transition: none;
  }
}
```

How this works

The control is a position:relative grid of grid-template-columns: repeat(N,1fr) so every segment is exactly equal width. Hidden radios drive the state; the single thumb is a sibling <span> whose width is calc(100%/N) and whose position is set by :has(#opt-k:checked) with a translate:calc(100% * k) — so the thumb math is derived from the grid, never from hard-coded pixels.

Because the thumb sizing is proportional, the control stays perfectly aligned at any width or font — the number one reason hand-built segmented controls drift. A spring-like cubic-bezier gives the thumb its signature settle.

.seg:has(#o2:checked) .thumb{ translate:100% 0 }
.seg:has(#o3:checked) .thumb{ translate:200% 0 }

Techniques used: equal-fraction grid track sizing · proportional thumb width via calc(100%/N) · :has() index-based translate · spring cubic-bezier settle · z-index layering label over thumb

Make it yours

  • Segment count: change the grid columns and add radio+label pairs — the thumb width auto-derives.
  • --accent sets the thumb fill; text flips to white on the active segment.
  • Swap the pill track for a boxed track by lowering the border-radius.
  • Slower/springier settle = tweak the thumb transition timing function.

Gotchas — read before shipping

  • All segments must be equal width for the proportional translate to land — always drive widths from the grid, never per-label padding.
  • Give the thumb a lower z-index than the labels, or it covers the text.
  • Use radios with a shared name so only one is ever active and arrow keys move the selection.
  • On very long labels the control can overflow; set a min-width:0 and text-overflow:ellipsis if labels are user-generated.

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. The grid + translate approach itself works everywhere; only the auto-sliding thumb needs :has().

Techniques used in this demo

Search CodeFronts

Loading…