25 CSS Button Groups
06 / 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 Split Button Dropdown with Native <details> (Primary Action + Menu)

The split button every app toolbar needs: a primary action fused to a caret that opens a dropdown of alternates — Save / Save as / Save a copy. Built on native <details>/<summary> so it opens with zero JavaScript, closes on outside interaction, and keeps full keyboard and screen-reader disclosure semantics. The definitive 'CSS split button dropdown' recipe.

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-06" aria-label="Split button dropdown demo">
  <div class="btn-06__split">
    <button type="button" class="btn-06__primary">Save changes</button>
    <details class="btn-06__more">
      <summary class="btn-06__caret" aria-label="More save options"></summary>
      <div class="btn-06__menu" role="menu">
        <button type="button" class="btn-06__item" role="menuitem">Save as…</button>
        <button type="button" class="btn-06__item" role="menuitem">Save a copy</button>
        <button type="button" class="btn-06__item" role="menuitem">Save as template</button>
        <div class="btn-06__sep"></div>
        <button type="button" class="btn-06__item btn-06__item--danger" role="menuitem">Discard changes</button>
      </div>
    </details>
  </div>
</section>
.btn-06,
.btn-06 *,
.btn-06 *::before,
.btn-06 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.btn-06 {
  --accent: oklch(0.72 0.17 55);
  --accent-d: oklch(0.62 0.17 55);
  --ink: #3a2508;
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  background: linear-gradient(160deg,#fff7ea,#ffedd0);
  padding: 120px 20px;
}

.btn-06__split {
  position: relative;
  display: inline-flex;
  border-radius: 14px;
  box-shadow: 0 12px 30px -14px rgba(150,90,10,.7);
}

.btn-06__primary {
  border: none;
  background: var(--accent);
  color: var(--ink);
  font-size: 15.5px;
  font-weight: 750;
  padding: 0 22px;
  height: 50px;
  border-radius: 14px 0 0 14px;
  cursor: pointer;
  transition: filter .15s;
}

.btn-06__primary:hover {
  filter: brightness(1.05);
}

.btn-06__more {
  position: relative;
}

.btn-06__caret {
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 50px;
  background: var(--accent-d);
  border-radius: 0 14px 14px 0;
  cursor: pointer;
  position: relative;
  border-left: 1px solid rgba(90,55,5,.25);
}

.btn-06__caret::-webkit-details-marker {
  display: none;
}

.btn-06__caret::marker {
  content: "";
}

.btn-06__caret::after {
  content: "";
  width: 9px;
  height: 9px;
  border-right: 2.5px solid var(--ink);
  border-bottom: 2.5px solid var(--ink);
  transform: translateY(-2px) rotate(45deg);
  transition: transform .3s;
}

.btn-06__more[open] .btn-06__caret::after {
  transform: translateY(2px) rotate(-135deg);
}

.btn-06__menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 220px;
  background: #fff;
  border: 1px solid #f0dcbb;
  border-radius: 14px;
  padding: 7px;
  z-index: 20;
  box-shadow: 0 24px 50px -22px rgba(120,70,5,.55);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.btn-06__more[open] .btn-06__menu {
  animation: btn-06-in .22s cubic-bezier(.2,.7,.3,1);
}

@keyframes btn-06-in {
  from {
    opacity: 0;
    translate: 0 -8px;
  }

  to {
    opacity: 1;
    translate: 0 0;
  }
}

.btn-06__item {
  text-align: left;
  border: none;
  background: none;
  font-size: 14.5px;
  font-weight: 600;
  color: #5a3f18;
  padding: 11px 13px;
  border-radius: 9px;
  cursor: pointer;
  transition: background .12s;
}

.btn-06__item:hover {
  background: #fdf1dd;
}

.btn-06__item--danger {
  color: #b23b2e;
}

.btn-06__item--danger:hover {
  background: #fdeae7;
}

.btn-06__sep {
  height: 1px;
  background: #f0dcbb;
  margin: 5px 6px;
}

.btn-06__primary:focus-visible,
.btn-06__caret:focus-visible,
.btn-06__item:focus-visible {
  outline: 3px solid var(--accent-d);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .btn-06__caret::after,
    .btn-06__more[open] .btn-06__menu {
    transition: none;
    animation: 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 Split Button Dropdown with Native <details> (Primary Action + Menu)
Source: https://codefronts.com/components/css-button-groups/css-split-button-dropdown-with-native-details-primary-action-menu/

The split button every app toolbar needs: a primary action fused to a caret that opens a dropdown of alternates — Save / Save as / Save a copy. Built on native <details>/<summary> so it opens with zero JavaScript, closes on outside interaction, and keeps full keyboard and screen-reader disclosure semantics. The definitive 'CSS split button dropdown' recipe.
## HTML
```html
<section class="btn-06" aria-label="Split button dropdown demo">
  <div class="btn-06__split">
    <button type="button" class="btn-06__primary">Save changes</button>
    <details class="btn-06__more">
      <summary class="btn-06__caret" aria-label="More save options"></summary>
      <div class="btn-06__menu" role="menu">
        <button type="button" class="btn-06__item" role="menuitem">Save as…</button>
        <button type="button" class="btn-06__item" role="menuitem">Save a copy</button>
        <button type="button" class="btn-06__item" role="menuitem">Save as template</button>
        <div class="btn-06__sep"></div>
        <button type="button" class="btn-06__item btn-06__item--danger" role="menuitem">Discard changes</button>
      </div>
    </details>
  </div>
</section>
```
## CSS
```css
.btn-06,
.btn-06 *,
.btn-06 *::before,
.btn-06 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.btn-06 {
  --accent: oklch(0.72 0.17 55);
  --accent-d: oklch(0.62 0.17 55);
  --ink: #3a2508;
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  background: linear-gradient(160deg,#fff7ea,#ffedd0);
  padding: 120px 20px;
}

.btn-06__split {
  position: relative;
  display: inline-flex;
  border-radius: 14px;
  box-shadow: 0 12px 30px -14px rgba(150,90,10,.7);
}

.btn-06__primary {
  border: none;
  background: var(--accent);
  color: var(--ink);
  font-size: 15.5px;
  font-weight: 750;
  padding: 0 22px;
  height: 50px;
  border-radius: 14px 0 0 14px;
  cursor: pointer;
  transition: filter .15s;
}

.btn-06__primary:hover {
  filter: brightness(1.05);
}

.btn-06__more {
  position: relative;
}

.btn-06__caret {
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 50px;
  background: var(--accent-d);
  border-radius: 0 14px 14px 0;
  cursor: pointer;
  position: relative;
  border-left: 1px solid rgba(90,55,5,.25);
}

.btn-06__caret::-webkit-details-marker {
  display: none;
}

.btn-06__caret::marker {
  content: "";
}

.btn-06__caret::after {
  content: "";
  width: 9px;
  height: 9px;
  border-right: 2.5px solid var(--ink);
  border-bottom: 2.5px solid var(--ink);
  transform: translateY(-2px) rotate(45deg);
  transition: transform .3s;
}

.btn-06__more[open] .btn-06__caret::after {
  transform: translateY(2px) rotate(-135deg);
}

.btn-06__menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 220px;
  background: #fff;
  border: 1px solid #f0dcbb;
  border-radius: 14px;
  padding: 7px;
  z-index: 20;
  box-shadow: 0 24px 50px -22px rgba(120,70,5,.55);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.btn-06__more[open] .btn-06__menu {
  animation: btn-06-in .22s cubic-bezier(.2,.7,.3,1);
}

@keyframes btn-06-in {
  from {
    opacity: 0;
    translate: 0 -8px;
  }

  to {
    opacity: 1;
    translate: 0 0;
  }
}

.btn-06__item {
  text-align: left;
  border: none;
  background: none;
  font-size: 14.5px;
  font-weight: 600;
  color: #5a3f18;
  padding: 11px 13px;
  border-radius: 9px;
  cursor: pointer;
  transition: background .12s;
}

.btn-06__item:hover {
  background: #fdf1dd;
}

.btn-06__item--danger {
  color: #b23b2e;
}

.btn-06__item--danger:hover {
  background: #fdeae7;
}

.btn-06__sep {
  height: 1px;
  background: #f0dcbb;
  margin: 5px 6px;
}

.btn-06__primary:focus-visible,
.btn-06__caret:focus-visible,
.btn-06__item:focus-visible {
  outline: 3px solid var(--accent-d);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .btn-06__caret::after,
    .btn-06__more[open] .btn-06__menu {
    transition: none;
    animation: none;
  }
}
```

How this works

The primary button and the caret are two halves of one rounded group. The caret half is a <summary> inside a <details>; clicking it toggles the menu natively — no click handlers, no outside-click listeners to wire. The menu is absolutely positioned below the group and revealed with a short [open] entrance animation.

The two halves share a seam: the primary action has a rounded left, the caret a rounded right, and a 1px divider between. The caret glyph is drawn with borders and rotates 180° on details[open]. Everything is focusable in a logical order, and Escape/blur close the menu because that is native <details> behavior.

.split details[open] .caret::after{ transform:rotate(-135deg) }
.split__menu{ position:absolute; top:calc(100% + 8px) }

Techniques used: native details/summary disclosure (no JS) · fused split-button seam with shared radii · border-drawn caret with [open] rotation · absolutely-positioned menu with entrance keyframes · :focus-visible rings on both halves and menu items

Make it yours

  • Primary color = --accent; the caret half derives a darker shade automatically.
  • Menu width and item padding are independent of the button width.
  • Open the menu upward by switching the menu's top to bottom.
  • Add dividers or section headings inside the menu freely — it is plain flow content.

Gotchas — read before shipping

  • Native <details> gives a checkbox-like disclosure role, not a true ARIA menu; for a strict menu pattern layer a few lines of JS adding role=menu. For most toolbars the native behavior is plenty and far more robust.
  • Keep the primary action a real <button>, separate from the summary, so its click never toggles the menu.
  • Give the menu a high z-index and a solid background so it never blends into content behind it.
  • Don't animate the menu with height transitions from display:none — use an [open] keyframe (transitions can't cross display changes).

Browser support

ChromeSafariFirefoxEdge
111+15.4+113+111+

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

details/summary is Baseline everywhere since 2020. The whole component is native — no feature-detection needed.

Techniques used in this demo

Search CodeFronts

Loading…