25 CSS Button Groups15 / 25

Pure CSSMIT licensed

CSS Split Button with Quantum Chevron Reveal (Animated Menu Materialize)

A split button where the dropdown doesn't just appear — it materializes: on opening, the caret splits into a quantum shimmer and menu items phase in one after another with a clip-path wipe and blur-in. A cinematic 'CSS split button quantum chevron reveal' built on native <details>, so the drama is pure CSS and the semantics stay honest.

Published Updated

Live Demo
Try it

The code

<section class="btn-15" aria-label="Quantum chevron split button demo">
  <div class="btn-15__split">
    <button type="button" class="btn-15__primary">Deploy</button>
    <details class="btn-15__more">
      <summary class="btn-15__caret" aria-label="More deploy options"></summary>
      <div class="btn-15__menu" role="menu">
        <button type="button" class="btn-15__item" role="menuitem">Deploy to preview</button>
        <button type="button" class="btn-15__item" role="menuitem">Deploy to staging</button>
        <button type="button" class="btn-15__item" role="menuitem">Deploy to production</button>
        <button type="button" class="btn-15__item" role="menuitem">Roll back last</button>
      </div>
    </details>
  </div>
</section>
.btn-15,
.btn-15 *,
.btn-15 *::before,
.btn-15 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.btn-15 {
  --a1: oklch(0.8 0.15 200);
  --a2: oklch(0.62 0.2 300);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  background: radial-gradient(120% 100% at 50% 0%,#0e1626,#060810);
  padding: 120px 20px;
}

.btn-15__split {
  position: relative;
  display: inline-flex;
  border-radius: 14px;
  box-shadow: 0 0 0 1px rgba(120,200,255,.2),0 20px 50px -24px rgba(30,120,255,.6);
}

.btn-15__primary {
  border: none;
  background: linear-gradient(120deg,var(--a1),var(--a2));
  color: #07101c;
  font-size: 15.5px;
  font-weight: 800;
  padding: 0 24px;
  height: 52px;
  border-radius: 14px 0 0 14px;
  cursor: pointer;
  transition: filter .15s;
}

.btn-15__primary:hover {
  filter: brightness(1.1);
}

.btn-15__more {
  position: relative;
}

.btn-15__caret {
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 50px;
  height: 52px;
  background: rgba(255,255,255,.06);
  border-radius: 0 14px 14px 0;
  cursor: pointer;
  position: relative;
  border-left: 1px solid rgba(150,210,255,.25);
  backdrop-filter: blur(4px);
}

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

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

.btn-15__caret::after,
.btn-15__caret::before {
  content: "";
  position: absolute;
  width: 9px;
  height: 9px;
  border-right: 2.5px solid var(--a1);
  border-bottom: 2.5px solid var(--a1);
  transform: translateY(-2px) rotate(45deg);
  transition: transform .3s,opacity .3s;
}

.btn-15__caret::before {
  border-color: var(--a2);
  opacity: 0;
}

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

.btn-15__more[open] .btn-15__caret::before {
  transform: translateY(0) rotate(-135deg) translateX(-3px);
  opacity: .8;
}

.btn-15__menu {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  min-width: 230px;
  background: rgba(14,22,40,.92);
  border: 1px solid rgba(120,200,255,.22);
  border-radius: 14px;
  padding: 7px;
  z-index: 20;
  box-shadow: 0 30px 60px -24px rgba(20,80,220,.7);
  display: flex;
  flex-direction: column;
  gap: 2px;
  backdrop-filter: blur(14px);
}

.btn-15__more[open] .btn-15__menu {
  animation: btn-15-wipe .35s cubic-bezier(.2,.7,.3,1) both;
}

@keyframes btn-15-wipe {
  from {
    clip-path: inset(0 0 100% 0 round 14px);
    opacity: 0;
  }

  to {
    clip-path: inset(0 0 0 0 round 14px);
    opacity: 1;
  }
}

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

.btn-15__more[open] .btn-15__item {
  animation: btn-15-phase .4s both;
}

.btn-15__more[open] .btn-15__item:nth-child(1) {
  animation-delay: .05s;
}

.btn-15__more[open] .btn-15__item:nth-child(2) {
  animation-delay: .11s;
}

.btn-15__more[open] .btn-15__item:nth-child(3) {
  animation-delay: .17s;
}

.btn-15__more[open] .btn-15__item:nth-child(4) {
  animation-delay: .23s;
}

@keyframes btn-15-phase {
  from {
    opacity: 0;
    filter: blur(6px);
    translate: 0 -6px;
  }

  to {
    opacity: 1;
    filter: blur(0);
    translate: 0 0;
  }
}

.btn-15__item:hover {
  background: rgba(120,200,255,.12);
}

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

@media (prefers-reduced-motion: reduce) {
  .btn-15__more[open] .btn-15__menu,
    .btn-15__more[open] .btn-15__item {
    animation: none;
  }

  .btn-15__caret::after,
    .btn-15__caret::before {
    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 Split Button with Quantum Chevron Reveal (Animated Menu Materialize)
Source: https://codefronts.com/components/css-button-groups/quantum-split/

A split button where the dropdown doesn't just appear — it materializes: on opening, the caret splits into a quantum shimmer and menu items phase in one after another with a clip-path wipe and blur-in. A cinematic 'CSS split button quantum chevron reveal' built on native <details>, so the drama is pure CSS and the semantics stay honest.
## HTML
```html
<section class="btn-15" aria-label="Quantum chevron split button demo">
  <div class="btn-15__split">
    <button type="button" class="btn-15__primary">Deploy</button>
    <details class="btn-15__more">
      <summary class="btn-15__caret" aria-label="More deploy options"></summary>
      <div class="btn-15__menu" role="menu">
        <button type="button" class="btn-15__item" role="menuitem">Deploy to preview</button>
        <button type="button" class="btn-15__item" role="menuitem">Deploy to staging</button>
        <button type="button" class="btn-15__item" role="menuitem">Deploy to production</button>
        <button type="button" class="btn-15__item" role="menuitem">Roll back last</button>
      </div>
    </details>
  </div>
</section>
```
## CSS
```css
.btn-15,
.btn-15 *,
.btn-15 *::before,
.btn-15 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.btn-15 {
  --a1: oklch(0.8 0.15 200);
  --a2: oklch(0.62 0.2 300);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  background: radial-gradient(120% 100% at 50% 0%,#0e1626,#060810);
  padding: 120px 20px;
}

.btn-15__split {
  position: relative;
  display: inline-flex;
  border-radius: 14px;
  box-shadow: 0 0 0 1px rgba(120,200,255,.2),0 20px 50px -24px rgba(30,120,255,.6);
}

.btn-15__primary {
  border: none;
  background: linear-gradient(120deg,var(--a1),var(--a2));
  color: #07101c;
  font-size: 15.5px;
  font-weight: 800;
  padding: 0 24px;
  height: 52px;
  border-radius: 14px 0 0 14px;
  cursor: pointer;
  transition: filter .15s;
}

.btn-15__primary:hover {
  filter: brightness(1.1);
}

.btn-15__more {
  position: relative;
}

.btn-15__caret {
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 50px;
  height: 52px;
  background: rgba(255,255,255,.06);
  border-radius: 0 14px 14px 0;
  cursor: pointer;
  position: relative;
  border-left: 1px solid rgba(150,210,255,.25);
  backdrop-filter: blur(4px);
}

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

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

.btn-15__caret::after,
.btn-15__caret::before {
  content: "";
  position: absolute;
  width: 9px;
  height: 9px;
  border-right: 2.5px solid var(--a1);
  border-bottom: 2.5px solid var(--a1);
  transform: translateY(-2px) rotate(45deg);
  transition: transform .3s,opacity .3s;
}

.btn-15__caret::before {
  border-color: var(--a2);
  opacity: 0;
}

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

.btn-15__more[open] .btn-15__caret::before {
  transform: translateY(0) rotate(-135deg) translateX(-3px);
  opacity: .8;
}

.btn-15__menu {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  min-width: 230px;
  background: rgba(14,22,40,.92);
  border: 1px solid rgba(120,200,255,.22);
  border-radius: 14px;
  padding: 7px;
  z-index: 20;
  box-shadow: 0 30px 60px -24px rgba(20,80,220,.7);
  display: flex;
  flex-direction: column;
  gap: 2px;
  backdrop-filter: blur(14px);
}

.btn-15__more[open] .btn-15__menu {
  animation: btn-15-wipe .35s cubic-bezier(.2,.7,.3,1) both;
}

@keyframes btn-15-wipe {
  from {
    clip-path: inset(0 0 100% 0 round 14px);
    opacity: 0;
  }

  to {
    clip-path: inset(0 0 0 0 round 14px);
    opacity: 1;
  }
}

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

.btn-15__more[open] .btn-15__item {
  animation: btn-15-phase .4s both;
}

.btn-15__more[open] .btn-15__item:nth-child(1) {
  animation-delay: .05s;
}

.btn-15__more[open] .btn-15__item:nth-child(2) {
  animation-delay: .11s;
}

.btn-15__more[open] .btn-15__item:nth-child(3) {
  animation-delay: .17s;
}

.btn-15__more[open] .btn-15__item:nth-child(4) {
  animation-delay: .23s;
}

@keyframes btn-15-phase {
  from {
    opacity: 0;
    filter: blur(6px);
    translate: 0 -6px;
  }

  to {
    opacity: 1;
    filter: blur(0);
    translate: 0 0;
  }
}

.btn-15__item:hover {
  background: rgba(120,200,255,.12);
}

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

@media (prefers-reduced-motion: reduce) {
  .btn-15__more[open] .btn-15__menu,
    .btn-15__more[open] .btn-15__item {
    animation: none;
  }

  .btn-15__caret::after,
    .btn-15__caret::before {
    transition: none;
  }
}
```

How this works

Structurally it's demo 03 — a fused primary + <summary> caret inside <details>. The theatre is in the [open] reveal: the menu uses a clip-path that wipes open from the caret, and each item carries a staggered animation-delay so they phase in sequentially with a blur-to-sharp filter transition — the "quantum materialize" effect.

The caret itself doubles: a real chevron plus a ghost copy that offsets and fades on open (an RGB-adjacent split) to suggest the item is de-cohering into the menu. All transform/opacity/filter, GPU-friendly, and it collapses instantly and accessibly because <details> owns the state.

.item{ animation:btn-15-phase .4s both }
.item:nth-child(2){ animation-delay:.06s } /* stagger */

Techniques used: native details/summary state · clip-path wipe reveal from the caret · staggered per-item phase-in with blur→sharp filter · ghost-chevron split on open · GPU-only transform/opacity/filter

Make it yours

  • Twin accent = --a1/--a2 (cyan→violet quantum pair).
  • Stagger speed = the per-item animation-delay step.
  • Wipe direction = the menu's opening clip-path geometry.
  • Tone down the ghost split for a subtler reveal.

Gotchas — read before shipping

  • Entrance animations must use animation (not transition) because the menu goes from display:none — transitions can't cross that.
  • Keep the primary action a separate <button> so its click never toggles the menu.
  • Staggered reveals should stay under ~250ms total or the menu feels laggy to power users.
  • Native <details> is disclosure semantics, not a strict ARIA menu — layer roles if you need the full menu pattern.

Browser support

ChromeSafariFirefoxEdge
111+15.4+113+111+

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

details/summary, clip-path and CSS animation are all Baseline. The effect degrades gracefully to an instant menu if animations are reduced.

Techniques used in this demo

Search CodeFronts

Loading…