25 CSS Button Groups
13 / 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 Button Group with Subgrid Underline Beam (Perfectly Aligned Tab Indicator)

A tab-style button group where a glowing underline beam snaps beneath the active button — and, crucially, aligns to the exact button column using CSS subgrid, so the beam is never a pixel off no matter the label lengths. The modern 'CSS subgrid underline beam tab indicator' pattern that finally kills manual offset math.

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-13" aria-label="Subgrid underline beam button group demo">
  <div class="btn-13__wrap">
    <div class="btn-13__group" role="group" aria-label="Dashboard sections">
      <input type="radio" name="btn-13" id="btn-13-1" class="btn-13__in" checked>
      <label for="btn-13-1" class="btn-13__tab">Overview</label>
      <input type="radio" name="btn-13" id="btn-13-2" class="btn-13__in">
      <label for="btn-13-2" class="btn-13__tab">Analytics</label>
      <input type="radio" name="btn-13" id="btn-13-3" class="btn-13__in">
      <label for="btn-13-3" class="btn-13__tab">Reports</label>
      <input type="radio" name="btn-13" id="btn-13-4" class="btn-13__in">
      <label for="btn-13-4" class="btn-13__tab">Settings</label>
      <div class="btn-13__track"><span class="btn-13__beam" aria-hidden="true"></span></div>
    </div>
  </div>
</section>
.btn-13,
.btn-13 *,
.btn-13 *::before,
.btn-13 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.btn-13 {
  --accent: oklch(0.72 0.19 45);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(180deg,#12100e,#191510);
  padding: 44px 20px;
}

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

.btn-13__group {
  display: grid;
  grid-template-columns: repeat(4,auto);
  justify-content: start;
  gap: 0 6px;
  position: relative;
}

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

.btn-13__tab {
  grid-row: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 0 20px;
  font-size: 15.5px;
  font-weight: 700;
  color: #8f857a;
  cursor: pointer;
  transition: color .25s;
  border-radius: 9px;
}

.btn-13__tab:hover {
  color: #d9cfc2;
}

.btn-13__in:checked + .btn-13__tab {
  color: #fff;
}

.btn-13__track {
  grid-row: 2;
  grid-column: 1/-1;
  display: grid;
  grid-template-columns: subgrid;
  height: 5px;
  margin-top: 6px;
}

.btn-13__beam {
  grid-column: 1;
  justify-self: stretch;
  height: 5px;
  border-radius: 3px;
  background: var(--accent);
  box-shadow: 0 0 16px 1px var(--accent),0 0 40px 2px color-mix(in oklab,var(--accent) 60%,transparent);
  transition: grid-column .05s;
  position: relative;
}

.btn-13__group:has(#btn-13-1:checked) .btn-13__beam {
  grid-column: 1;
}

.btn-13__group:has(#btn-13-2:checked) .btn-13__beam {
  grid-column: 2;
}

.btn-13__group:has(#btn-13-3:checked) .btn-13__beam {
  grid-column: 3;
}

.btn-13__group:has(#btn-13-4:checked) .btn-13__beam {
  grid-column: 4;
}

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

@supports not (grid-template-columns: subgrid) {
  .btn-13__track {
    grid-template-columns: repeat(4,1fr);
  }
}

@media (prefers-reduced-motion: reduce) {
  .btn-13__tab {
    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 Button Group with Subgrid Underline Beam (Perfectly Aligned Tab Indicator)
Source: https://codefronts.com/components/css-button-groups/subgrid-beam/

A tab-style button group where a glowing underline beam snaps beneath the active button — and, crucially, aligns to the exact button column using CSS subgrid, so the beam is never a pixel off no matter the label lengths. The modern 'CSS subgrid underline beam tab indicator' pattern that finally kills manual offset math.
## HTML
```html
<section class="btn-13" aria-label="Subgrid underline beam button group demo">
  <div class="btn-13__wrap">
    <div class="btn-13__group" role="group" aria-label="Dashboard sections">
      <input type="radio" name="btn-13" id="btn-13-1" class="btn-13__in" checked>
      <label for="btn-13-1" class="btn-13__tab">Overview</label>
      <input type="radio" name="btn-13" id="btn-13-2" class="btn-13__in">
      <label for="btn-13-2" class="btn-13__tab">Analytics</label>
      <input type="radio" name="btn-13" id="btn-13-3" class="btn-13__in">
      <label for="btn-13-3" class="btn-13__tab">Reports</label>
      <input type="radio" name="btn-13" id="btn-13-4" class="btn-13__in">
      <label for="btn-13-4" class="btn-13__tab">Settings</label>
      <div class="btn-13__track"><span class="btn-13__beam" aria-hidden="true"></span></div>
    </div>
  </div>
</section>
```
## CSS
```css
.btn-13,
.btn-13 *,
.btn-13 *::before,
.btn-13 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.btn-13 {
  --accent: oklch(0.72 0.19 45);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(180deg,#12100e,#191510);
  padding: 44px 20px;
}

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

.btn-13__group {
  display: grid;
  grid-template-columns: repeat(4,auto);
  justify-content: start;
  gap: 0 6px;
  position: relative;
}

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

.btn-13__tab {
  grid-row: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 0 20px;
  font-size: 15.5px;
  font-weight: 700;
  color: #8f857a;
  cursor: pointer;
  transition: color .25s;
  border-radius: 9px;
}

.btn-13__tab:hover {
  color: #d9cfc2;
}

.btn-13__in:checked + .btn-13__tab {
  color: #fff;
}

.btn-13__track {
  grid-row: 2;
  grid-column: 1/-1;
  display: grid;
  grid-template-columns: subgrid;
  height: 5px;
  margin-top: 6px;
}

.btn-13__beam {
  grid-column: 1;
  justify-self: stretch;
  height: 5px;
  border-radius: 3px;
  background: var(--accent);
  box-shadow: 0 0 16px 1px var(--accent),0 0 40px 2px color-mix(in oklab,var(--accent) 60%,transparent);
  transition: grid-column .05s;
  position: relative;
}

.btn-13__group:has(#btn-13-1:checked) .btn-13__beam {
  grid-column: 1;
}

.btn-13__group:has(#btn-13-2:checked) .btn-13__beam {
  grid-column: 2;
}

.btn-13__group:has(#btn-13-3:checked) .btn-13__beam {
  grid-column: 3;
}

.btn-13__group:has(#btn-13-4:checked) .btn-13__beam {
  grid-column: 4;
}

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

@supports not (grid-template-columns: subgrid) {
  .btn-13__track {
    grid-template-columns: repeat(4,1fr);
  }
}

@media (prefers-reduced-motion: reduce) {
  .btn-13__tab {
    transition: none;
  }
}
```

How this works

The group is a two-row grid: row 1 holds the buttons across repeat(4,auto) columns; row 2 is a beam track declared with grid-template-columns: subgrid so it inherits the exact same column lines as the buttons above. The beam is a single cell placed with grid-column, and :has() moves it to the checked button's column — the browser resizes and positions it to that column automatically.

Because subgrid shares the parent's tracks, the beam width always equals the button width with zero measurement — the long-standing pain of animated tab underlines. A blurred duplicate behind the beam gives it a neon bloom.

.track{ display:grid; grid-template-columns:subgrid; grid-column:1/-1 }
.group:has(#t3:checked) .beam{ grid-column:3 }

Techniques used: CSS subgrid column inheritance for exact alignment · :has() to reposition the beam cell by grid-column · neon bloom via blurred duplicate · grid-column transition for the slide · content-sized buttons (no equal-width constraint)

Make it yours

  • Beam color + bloom = --accent; the blurred copy inherits it.
  • Add tabs freely — subgrid re-derives the columns, no offset edits.
  • Thicken the beam by growing the track row height.
  • Swap the underline beam for a top beam by moving the track to row 1.

Gotchas — read before shipping

  • Subgrid must be on the axis you're aligning — here grid-template-columns:subgrid on a full-width track (grid-column:1/-1).
  • Animating grid-column jumps in some engines; for buttery slides, animate the beam's translate within its cell instead, or accept the snap.
  • Keep buttons as real radios/buttons so the group stays keyboard-navigable.
  • Subgrid is Baseline 2023 but landed in Chrome late (117) — provide a static underline fallback for very old browsers.

Browser support

ChromeSafariFirefoxEdge
117+16+71+117+

Subgrid is Baseline 2023. Without it the beam falls back to sitting under the first tab — visible but not aligned; feature-query with @supports(grid-template-columns:subgrid).

Techniques used in this demo

Search CodeFronts

Loading…