25 CSS Button Groups
22 / 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 Filter Button Group with RGB-Split Glitch Active State (Cyberpunk Toggle)

A filter button group with a cyberpunk twist: selecting a filter fires a brief RGB-split glitch — the label tears into cyan and magenta ghosts, jitters through clip-path slices, then locks into a neon active state. The head-turning 'CSS RGB-split glitch active state button group' for gaming, music and creative-tool UIs.

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-22" aria-label="RGB-split glitch filter group demo">
  <div class="btn-22__wrap">
    <p class="btn-22__label">Audio filter</p>
    <div class="btn-22__group" role="radiogroup" aria-label="Audio filter">
      <input type="radio" name="btn-22" id="btn-22-1" class="btn-22__in" checked>
      <label for="btn-22-1" class="btn-22__btn" data-txt="CLEAN">CLEAN</label>
      <input type="radio" name="btn-22" id="btn-22-2" class="btn-22__in">
      <label for="btn-22-2" class="btn-22__btn" data-txt="BASS">BASS</label>
      <input type="radio" name="btn-22" id="btn-22-3" class="btn-22__in">
      <label for="btn-22-3" class="btn-22__btn" data-txt="VOCAL">VOCAL</label>
      <input type="radio" name="btn-22" id="btn-22-4" class="btn-22__in">
      <label for="btn-22-4" class="btn-22__btn" data-txt="LO-FI">LO-FI</label>
    </div>
  </div>
</section>
.btn-22,
.btn-22 *,
.btn-22 *::before,
.btn-22 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.btn-22 {
  --accent: oklch(0.78 0.2 155);
  font-family: ui-monospace,'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #050506;
  padding: 44px 20px;
}

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

.btn-22__label {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: #5a5a62;
  margin: 0 0 16px 2px;
}

.btn-22__group {
  display: grid;
  grid-template-columns: repeat(4,1fr);
  gap: 10px;
}

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

.btn-22__btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: .1em;
  color: #7d7d86;
  background: #101014;
  border: 1.5px solid #22222a;
  border-radius: 10px;
  cursor: pointer;
  transition: color .2s,border-color .2s,background .2s;
}

.btn-22__btn::before,
.btn-22__btn::after {
  content: attr(data-txt);
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
}

.btn-22__btn::before {
  color: #00f6ff;
}

.btn-22__btn::after {
  color: #ff2bd0;
}

.btn-22__btn:hover {
  color: #c7c7d0;
  border-color: #33333f;
}

.btn-22__in:checked + .btn-22__btn {
  color: var(--accent);
  border-color: var(--accent);
  background: #0c1410;
  text-shadow: 0 0 10px color-mix(in oklab,var(--accent) 70%,transparent);
  box-shadow: 0 0 20px -8px var(--accent),inset 0 0 12px -8px var(--accent);
  animation: btn-22-glitch .45s steps(2,end);
}

.btn-22__in:checked + .btn-22__btn::before {
  animation: btn-22-c .45s steps(2,end);
}

.btn-22__in:checked + .btn-22__btn::after {
  animation: btn-22-m .45s steps(2,end);
}

@keyframes btn-22-glitch {
  0% {
    clip-path: inset(0 0 0 0);
  }

  20% {
    clip-path: inset(20% 0 55% 0);
  }

  40% {
    clip-path: inset(65% 0 12% 0);
  }

  60% {
    clip-path: inset(35% 0 40% 0);
  }

  80% {
    clip-path: inset(10% 0 75% 0);
  }

  100% {
    clip-path: inset(0 0 0 0);
  }
}

@keyframes btn-22-c {
  0%,
    100% {
    opacity: 0;
    translate: 0 0;
  }

  15% {
    opacity: .9;
    translate: -4px -2px;
  }

  45% {
    opacity: .9;
    translate: 3px 1px;
  }

  70% {
    opacity: .6;
    translate: -2px 2px;
  }
}

@keyframes btn-22-m {
  0%,
    100% {
    opacity: 0;
    translate: 0 0;
  }

  15% {
    opacity: .9;
    translate: 4px 2px;
  }

  45% {
    opacity: .9;
    translate: -3px -1px;
  }

  70% {
    opacity: .6;
    translate: 2px -2px;
  }
}

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

@media (prefers-reduced-motion: reduce) {
  .btn-22__in:checked + .btn-22__btn,
    .btn-22__in:checked + .btn-22__btn::before,
    .btn-22__in:checked + .btn-22__btn::after {
    animation: none;
  }

  .btn-22__btn::before,
    .btn-22__btn::after {
    display: 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 Filter Button Group with RGB-Split Glitch Active State (Cyberpunk Toggle)
Source: https://codefronts.com/components/css-button-groups/glitch-grid/

A filter button group with a cyberpunk twist: selecting a filter fires a brief RGB-split glitch — the label tears into cyan and magenta ghosts, jitters through clip-path slices, then locks into a neon active state. The head-turning 'CSS RGB-split glitch active state button group' for gaming, music and creative-tool UIs.
## HTML
```html
<section class="btn-22" aria-label="RGB-split glitch filter group demo">
  <div class="btn-22__wrap">
    <p class="btn-22__label">Audio filter</p>
    <div class="btn-22__group" role="radiogroup" aria-label="Audio filter">
      <input type="radio" name="btn-22" id="btn-22-1" class="btn-22__in" checked>
      <label for="btn-22-1" class="btn-22__btn" data-txt="CLEAN">CLEAN</label>
      <input type="radio" name="btn-22" id="btn-22-2" class="btn-22__in">
      <label for="btn-22-2" class="btn-22__btn" data-txt="BASS">BASS</label>
      <input type="radio" name="btn-22" id="btn-22-3" class="btn-22__in">
      <label for="btn-22-3" class="btn-22__btn" data-txt="VOCAL">VOCAL</label>
      <input type="radio" name="btn-22" id="btn-22-4" class="btn-22__in">
      <label for="btn-22-4" class="btn-22__btn" data-txt="LO-FI">LO-FI</label>
    </div>
  </div>
</section>
```
## CSS
```css
.btn-22,
.btn-22 *,
.btn-22 *::before,
.btn-22 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.btn-22 {
  --accent: oklch(0.78 0.2 155);
  font-family: ui-monospace,'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #050506;
  padding: 44px 20px;
}

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

.btn-22__label {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: #5a5a62;
  margin: 0 0 16px 2px;
}

.btn-22__group {
  display: grid;
  grid-template-columns: repeat(4,1fr);
  gap: 10px;
}

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

.btn-22__btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: .1em;
  color: #7d7d86;
  background: #101014;
  border: 1.5px solid #22222a;
  border-radius: 10px;
  cursor: pointer;
  transition: color .2s,border-color .2s,background .2s;
}

.btn-22__btn::before,
.btn-22__btn::after {
  content: attr(data-txt);
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
}

.btn-22__btn::before {
  color: #00f6ff;
}

.btn-22__btn::after {
  color: #ff2bd0;
}

.btn-22__btn:hover {
  color: #c7c7d0;
  border-color: #33333f;
}

.btn-22__in:checked + .btn-22__btn {
  color: var(--accent);
  border-color: var(--accent);
  background: #0c1410;
  text-shadow: 0 0 10px color-mix(in oklab,var(--accent) 70%,transparent);
  box-shadow: 0 0 20px -8px var(--accent),inset 0 0 12px -8px var(--accent);
  animation: btn-22-glitch .45s steps(2,end);
}

.btn-22__in:checked + .btn-22__btn::before {
  animation: btn-22-c .45s steps(2,end);
}

.btn-22__in:checked + .btn-22__btn::after {
  animation: btn-22-m .45s steps(2,end);
}

@keyframes btn-22-glitch {
  0% {
    clip-path: inset(0 0 0 0);
  }

  20% {
    clip-path: inset(20% 0 55% 0);
  }

  40% {
    clip-path: inset(65% 0 12% 0);
  }

  60% {
    clip-path: inset(35% 0 40% 0);
  }

  80% {
    clip-path: inset(10% 0 75% 0);
  }

  100% {
    clip-path: inset(0 0 0 0);
  }
}

@keyframes btn-22-c {
  0%,
    100% {
    opacity: 0;
    translate: 0 0;
  }

  15% {
    opacity: .9;
    translate: -4px -2px;
  }

  45% {
    opacity: .9;
    translate: 3px 1px;
  }

  70% {
    opacity: .6;
    translate: -2px 2px;
  }
}

@keyframes btn-22-m {
  0%,
    100% {
    opacity: 0;
    translate: 0 0;
  }

  15% {
    opacity: .9;
    translate: 4px 2px;
  }

  45% {
    opacity: .9;
    translate: -3px -1px;
  }

  70% {
    opacity: .6;
    translate: 2px -2px;
  }
}

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

@media (prefers-reduced-motion: reduce) {
  .btn-22__in:checked + .btn-22__btn,
    .btn-22__in:checked + .btn-22__btn::before,
    .btn-22__in:checked + .btn-22__btn::after {
    animation: none;
  }

  .btn-22__btn::before,
    .btn-22__btn::after {
    display: none;
  }
}
```

How this works

Real radios drive single-select filtering. On :checked, the label runs a short one-shot glitch animation: two pseudo-elements clone the text via content:attr(data-txt), offset in opposite directions and tinted cyan / magenta (the RGB split), while a clip-path keyframe slices the label into shifting horizontal bands for the tear.

The glitch is deliberately brief (~450ms) so it punctuates the selection rather than annoying. After it resolves, the active button holds a clean neon state — border glow and bright text. It's all transforms, clip-path and pseudo-content, so it composites cheaply and never touches layout.

.label::before{ content:attr(data-txt); color:#0ff; /* cyan ghost */ }
.in:checked + .label{ animation:btn-22-glitch .45s steps(2,end) }

Techniques used: attr(data-txt) text clones for RGB split · clip-path band-slice glitch keyframes · cyan/magenta channel offset · one-shot punctuating animation · neon hold state · reduced-motion skips straight to active

Make it yours

  • Split channels = the ::before/::after colors (cyan/magenta here).
  • Glitch length/intensity = the btn-22-glitch duration + offsets.
  • --accent sets the resolved neon hold color.
  • Increase band count in the clip-path keyframe for a harsher tear.

Gotchas — read before shipping

  • Glitch effects can trigger motion sensitivity — keep them brief and fully removed under prefers-reduced-motion (jump straight to active).
  • Mirror the visible label into data-txt so the ghosts match; keep it in sync if labels are dynamic.
  • Don't glitch on every hover — reserve it for the selection moment so it stays special.
  • Ensure the resolved state (not the glitch) carries the accessible contrast.

Browser support

ChromeSafariFirefoxEdge
111+16.2+113+111+

Floor set by oklch(), color-mix() as this demo is written. The core technique itself goes back further — Chrome all.

attr(), clip-path and CSS animation are Baseline. The effect is decorative; the radio selection is fully functional everywhere.

Techniques used in this demo

Search CodeFronts

Loading…