43 examples Responsive Uses JS beginner

43 CSS Button Designs

Advertisement

A CSS button is the primary call-to-action element on any page — the control that gets clicked. These 43 hand-coded designs are ready-to-ship buttons for forms, hero CTAs, toolbars, and inline actions — copy the markup, drop in your label, and ship.

43 unique buttons 25 Pure CSS 18 CSS + JS 0 dependencies 100% copy-paste ready Published
Updated · 12 new designs added ·
01 / 43
Add to Cart
CSS + JS NEW
A real e-commerce state machine: click fills a green progress bar along the bottom edge, snaps to a success state with a particle burst, then auto-resets after two seconds.
Try it
.btn-cart-surface {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 40px;
  background: #f2efe9;
  border-radius: 16px;
}
.btn-cart {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-width: 200px;
  padding: 15px 30px;
  border: none;
  border-radius: 14px;
  background: #1a1814;
  color: #fff;
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  overflow: hidden;
  user-select: none;
  transition: background 0.3s ease, transform 0.15s ease;
}
.btn-cart:hover { background: #333; transform: translateY(-1px); }
.btn-cart:active { transform: scale(0.97); }
.btn-cart-icon { font-size: 18px; transition: transform 0.3s, opacity 0.3s; }
.btn-cart-text { transition: opacity 0.2s; }
.btn-cart-bar {
  position: absolute;
  left: 0; bottom: 0;
  height: 3px;
  width: 0%;
  background: #4ade80;
  border-radius: 0 0 14px 14px;
}
.btn-cart-check {
  position: absolute;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 600;
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.3s, transform 0.3s;
}
.btn-cart.is-loading { pointer-events: none; }
.btn-cart.is-loading .btn-cart-bar {
  width: 100%;
  transition: width 1.2s cubic-bezier(.4,0,.2,1);
}
.btn-cart.is-success { background: #16a34a; pointer-events: none; }
.btn-cart.is-success .btn-cart-icon,
.btn-cart.is-success .btn-cart-text { opacity: 0; transform: translateY(-10px); }
.btn-cart.is-success .btn-cart-check { opacity: 1; transform: translateY(0); }
.btn-cart-particles { position: absolute; inset: 0; pointer-events: none; overflow: visible; }
.btn-cart-particle {
  position: absolute;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: #4ade80;
  animation: btn-cart-pop 0.6s cubic-bezier(.22,1,.36,1) forwards;
}
@keyframes btn-cart-pop {
  0%   { transform: translate(0,0) scale(1); opacity: 1; }
  100% { transform: translate(var(--tx), var(--ty)) scale(0); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-cart-bar, .btn-cart-icon, .btn-cart-text, .btn-cart-check { transition: none; }
}
<div class="btn-cart-surface">
  <button class="btn-cart">
    <span class="btn-cart-icon" aria-hidden="true">🛒</span>
    <span class="btn-cart-text">Add to Cart</span>
    <span class="btn-cart-bar" aria-hidden="true"></span>
    <span class="btn-cart-check">✓&nbsp;Added!</span>
    <span class="btn-cart-particles" aria-hidden="true"></span>
  </button>
</div>
document.querySelectorAll('.btn-cart').forEach(function (btn) {
  var busy = false;
  btn.addEventListener('click', function () {
    if (busy) return;
    busy = true;
    var bar = btn.querySelector('.btn-cart-bar');
    var wrap = btn.querySelector('.btn-cart-particles');
    btn.classList.add('is-loading');
    setTimeout(function () {
      btn.classList.remove('is-loading');
      btn.classList.add('is-success');
      var colors = ['#4ade80', '#86efac', '#bbf7d0'];
      for (var i = 0; i < 12; i++) {
        var p = document.createElement('div');
        p.className = 'btn-cart-particle';
        var angle = (i / 12) * Math.PI * 2;
        var dist = 20 + Math.random() * 35;
        p.style.left = (btn.offsetWidth / 2) + 'px';
        p.style.top = (btn.offsetHeight / 2) + 'px';
        p.style.setProperty('--tx', (Math.cos(angle) * dist) + 'px');
        p.style.setProperty('--ty', (Math.sin(angle) * dist) + 'px');
        p.style.background = colors[i % 3];
        p.style.animationDelay = (Math.random() * 0.1) + 's';
        wrap.appendChild(p);
        p.addEventListener('animationend', function () { this.remove(); });
      }
      setTimeout(function () {
        btn.classList.remove('is-success');
        bar.style.transition = 'none';
        bar.style.width = '0%';
        requestAnimationFrame(function () {
          bar.style.transition = '';
          busy = false;
        });
      }, 2200);
    }, 1400);
  });
});
02 / 43
Download Progress
CSS + JS NEW
The button body itself fills left-to-right like a true progress indicator while the arrow bounces, then morphs into a "Complete" state with a checkmark.
Try it
.btn-dl-surface {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 40px;
  background: #f2efe9;
  border-radius: 16px;
}
.btn-dl {
  position: relative;
  width: 200px;
  height: 52px;
  border: 2px solid #1a1814;
  border-radius: 26px;
  background: transparent;
  cursor: pointer;
  overflow: hidden;
  user-select: none;
}
.btn-dl-fill {
  position: absolute;
  inset: 0;
  background: #1a1814;
  border-radius: 24px;
  transform: translateX(-100%);
}
.btn-dl-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  z-index: 1;
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: #1a1814;
  transition: color 0.2s;
}
.btn-dl-arrow { font-size: 16px; }
.btn-dl-done {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: #fff;
  opacity: 0;
  transform: scale(0.8);
  transition: opacity 0.3s, transform 0.3s;
}
.btn-dl.is-loading .btn-dl-fill {
  transform: translateX(0);
  transition: transform 1.4s cubic-bezier(.4,0,.2,1);
}
.btn-dl.is-loading .btn-dl-label { color: #fff; }
.btn-dl.is-loading .btn-dl-arrow { animation: btn-dl-bounce 0.6s infinite alternate; }
@keyframes btn-dl-bounce {
  from { transform: translateY(-3px); }
  to   { transform: translateY(3px); }
}
.btn-dl.is-done .btn-dl-fill { transform: translateX(0); }
.btn-dl.is-done .btn-dl-label { opacity: 0; }
.btn-dl.is-done .btn-dl-done { opacity: 1; transform: scale(1); }
@media (prefers-reduced-motion: reduce) {
  .btn-dl.is-loading .btn-dl-arrow { animation: none; }
}
<div class="btn-dl-surface">
  <button class="btn-dl">
    <span class="btn-dl-fill" aria-hidden="true"></span>
    <span class="btn-dl-label">
      <span class="btn-dl-arrow" aria-hidden="true">↓</span>
      <span>Download File</span>
    </span>
    <span class="btn-dl-done">✓&nbsp;Complete</span>
  </button>
</div>
document.querySelectorAll('.btn-dl').forEach(function (btn) {
  var busy = false;
  btn.addEventListener('click', function () {
    if (busy) return;
    busy = true;
    btn.classList.add('is-loading');
    setTimeout(function () {
      btn.classList.remove('is-loading');
      btn.classList.add('is-done');
      setTimeout(function () {
        btn.classList.remove('is-done');
        busy = false;
      }, 2500);
    }, 1600);
  });
});
03 / 43
Like Heart
CSS + JS NEW
A toggle that bursts seven floating heart particles upward on activation; the count ticks up or down with a sliding number animation.
Try it
.btn-like {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 22px;
  border: 1.5px solid #e0dbd3;
  border-radius: 50px;
  background: transparent;
  cursor: pointer;
  overflow: visible;
  user-select: none;
  transition: border-color 0.3s, background 0.3s;
}
.btn-like:hover { border-color: #f43f5e; background: #fff0f3; }
.btn-like-heart {
  font-size: 22px;
  line-height: 1;
  filter: grayscale(1);
  transition: transform 0.15s cubic-bezier(.34,1.56,.64,1);
}
.btn-like-count {
  display: flex;
  align-items: center;
  height: 20px;
  min-width: 24px;
  overflow: hidden;
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: 14px;
  font-weight: 600;
  color: #888;
  transition: color 0.3s;
}
.btn-like.is-liked { border-color: #f43f5e; background: #fff0f3; }
.btn-like.is-liked .btn-like-heart {
  filter: none;
  animation: btn-like-pop 0.4s cubic-bezier(.34,1.56,.64,1) forwards;
}
.btn-like.is-liked .btn-like-count { color: #f43f5e; }
@keyframes btn-like-pop {
  0%   { transform: scale(0.8); }
  50%  { transform: scale(1.4); }
  100% { transform: scale(1); }
}
.btn-like-particle {
  position: absolute;
  font-size: var(--fs, 12px);
  line-height: 1;
  pointer-events: none;
  z-index: 10;
  animation: btn-like-fly 0.7s cubic-bezier(.22,1,.36,1) forwards;
}
@keyframes btn-like-fly {
  0%   { transform: translate(0,0) scale(1); opacity: 1; }
  100% { transform: translate(var(--tx), var(--ty)) scale(0.3); opacity: 0; }
}
.btn-like-slide {
  display: inline-block;
  animation: btn-like-count-up 0.3s cubic-bezier(.22,1,.36,1) forwards;
}
@keyframes btn-like-count-up {
  from { transform: translateY(100%); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-like-heart, .btn-like-slide, .btn-like-particle { animation: none; }
}
<button class="btn-like" aria-pressed="false">
  <span class="btn-like-heart" aria-hidden="true">🤍</span>
  <span class="btn-like-count">2.4k</span>
</button>
document.querySelectorAll('.btn-like').forEach(function (btn) {
  var liked = false;
  var count = 2400;
  var heart = btn.querySelector('.btn-like-heart');
  var label = btn.querySelector('.btn-like-count');
  btn.addEventListener('click', function () {
    liked = !liked;
    btn.classList.toggle('is-liked', liked);
    btn.setAttribute('aria-pressed', String(liked));
    heart.textContent = liked ? '❤️' : '🤍';
    count += liked ? 1 : -1;
    var text = count >= 1000 ? (count / 1000).toFixed(1) + 'k' : String(count);
    var span = document.createElement('span');
    span.className = 'btn-like-slide';
    span.textContent = text;
    label.innerHTML = '';
    label.appendChild(span);
    if (liked) {
      var cx = heart.offsetLeft + heart.offsetWidth / 2;
      var cy = heart.offsetTop + heart.offsetHeight / 2;
      for (var i = 0; i < 7; i++) {
        var p = document.createElement('span');
        p.className = 'btn-like-particle';
        p.textContent = '❤️';
        var angle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI * 1.5;
        var dist = 25 + Math.random() * 35;
        p.style.left = cx + 'px';
        p.style.top = cy + 'px';
        p.style.setProperty('--tx', (Math.cos(angle) * dist) + 'px');
        p.style.setProperty('--ty', (Math.sin(angle) * dist) + 'px');
        p.style.setProperty('--fs', (9 + Math.random() * 7) + 'px');
        p.style.animationDelay = (Math.random() * 0.15) + 's';
        btn.appendChild(p);
        p.addEventListener('animationend', function () { this.remove(); });
      }
    }
  });
});
04 / 43
Confirm Delete
CSS + JS NEW
A two-step destructive pattern: the first click shakes the button and slides up a red confirmation panel; a second click within three seconds wipes it with a sweep into a "Deleted" ghost state.
Try it
.btn-del {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-width: 170px;
  padding: 13px 22px;
  border: 1.5px solid #fca5a5;
  border-radius: 10px;
  background: #fff0f0;
  color: #dc2626;
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  overflow: hidden;
  user-select: none;
  transition: background 0.3s, border-color 0.3s;
}
.btn-del:hover { background: #fee2e2; border-color: #f87171; }
.btn-del-icon { font-size: 16px; }
.btn-del-text { transition: opacity 0.2s; }
.btn-del-confirm {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: #dc2626;
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  opacity: 0;
  transform: translateY(100%);
  transition: opacity 0.25s, transform 0.25s cubic-bezier(.22,1,.36,1);
}
.btn-del-wipe {
  position: absolute;
  inset: 0;
  z-index: 5;
  background: #dc2626;
  border-radius: 8px;
  transform: scaleX(0);
  transform-origin: left;
}
.btn-del.is-confirming { animation: btn-del-shake 0.4s cubic-bezier(.36,.07,.19,.97); }
.btn-del.is-confirming .btn-del-confirm { opacity: 1; transform: translateY(0); }
.btn-del.is-confirming .btn-del-text,
.btn-del.is-confirming .btn-del-icon { opacity: 0; }
@keyframes btn-del-shake {
  0%,100% { transform: translateX(0); }
  20% { transform: translateX(-5px) rotate(-1deg); }
  40% { transform: translateX(5px) rotate(1deg); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(3px); }
}
.btn-del.is-deleting { pointer-events: none; overflow: visible; }
.btn-del.is-deleting .btn-del-wipe { animation: btn-del-wipe-out 0.5s cubic-bezier(.77,0,.18,1) forwards; }
@keyframes btn-del-wipe-out {
  0%   { transform: scaleX(0); opacity: 1; }
  60%  { transform: scaleX(1); opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}
.btn-del.is-deleted {
  border-color: #e5e7eb;
  background: #f9fafb;
  color: #9ca3af;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .btn-del.is-confirming { animation: none; }
  .btn-del.is-deleting .btn-del-wipe { animation: none; }
}
<button class="btn-del">
  <span class="btn-del-icon" aria-hidden="true">🗑</span>
  <span class="btn-del-text">Delete Item</span>
  <span class="btn-del-confirm">⚠ Tap again to confirm</span>
  <span class="btn-del-wipe" aria-hidden="true"></span>
</button>
document.querySelectorAll('.btn-del').forEach(function (btn) {
  var state = 'idle';
  var resetTimer;
  btn.addEventListener('click', function () {
    if (state === 'idle') {
      state = 'confirming';
      btn.classList.add('is-confirming');
      resetTimer = setTimeout(function () {
        if (state === 'confirming') {
          state = 'idle';
          btn.classList.remove('is-confirming');
        }
      }, 3000);
    } else if (state === 'confirming') {
      clearTimeout(resetTimer);
      state = 'deleting';
      btn.classList.add('is-deleting');
      setTimeout(function () {
        btn.classList.remove('is-confirming', 'is-deleting');
        btn.classList.add('is-deleted');
        btn.querySelector('.btn-del-icon').textContent = '✓';
        btn.querySelector('.btn-del-icon').style.opacity = '1';
        btn.querySelector('.btn-del-text').textContent = 'Deleted';
        btn.querySelector('.btn-del-text').style.opacity = '1';
        btn.querySelector('.btn-del-confirm').style.opacity = '0';
      }, 600);
    }
  });
});
05 / 43
Play Pause
CSS + JS NEW
Midnight Drive 3:42 · Synthwave
A media control whose icon morphs between a triangle and pause bars; a pulsing halo ring appears while playing and a row of waveform bars dances with staggered timing.
Try it
.btn-pp-surface {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 28px 36px;
  background: #f2efe9;
  border-radius: 16px;
}
.btn-pp-area { display: flex; align-items: center; gap: 16px; }
.btn-pp-info { display: flex; flex-direction: column; gap: 6px; }
.btn-pp-meta { display: flex; flex-direction: column; gap: 2px; }
.btn-pp-title {
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: #1a1814;
}
.btn-pp-sub {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 9px;
  letter-spacing: 1px;
  color: #b0aaa0;
}
.btn-pp {
  position: relative;
  width: 64px;
  height: 64px;
  flex-shrink: 0;
  border: none;
  border-radius: 50%;
  background: #1a1814;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.2s cubic-bezier(.34,1.56,.64,1), background 0.3s;
}
.btn-pp:hover { transform: scale(1.1); background: #333; }
.btn-pp:active { transform: scale(0.93); }
.btn-pp-play {
  position: relative;
  z-index: 1;
  margin-left: 3px;
  font-size: 24px;
  line-height: 1;
  color: #fff;
  transition: opacity 0.15s, transform 0.15s;
}
.btn-pp-pause {
  position: absolute;
  z-index: 1;
  display: flex;
  gap: 5px;
  opacity: 0;
  transform: scale(0.5);
  transition: opacity 0.2s, transform 0.2s;
}
.btn-pp-pause span { width: 4px; height: 20px; border-radius: 2px; background: #fff; }
.btn-pp.is-playing .btn-pp-play { opacity: 0; transform: scale(0); }
.btn-pp.is-playing .btn-pp-pause { opacity: 1; transform: scale(1); }
.btn-pp-ring {
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  border: 2px solid rgba(26,24,20,0.2);
}
.btn-pp.is-playing .btn-pp-ring { animation: btn-pp-ring 2s ease-in-out infinite; }
@keyframes btn-pp-ring {
  0%,100% { transform: scale(1); opacity: 0.5; }
  50%     { transform: scale(1.15); opacity: 0.15; }
}
.btn-pp-wave {
  display: flex;
  align-items: center;
  gap: 4px;
  height: 36px;
}
.btn-pp-bar {
  width: 3px;
  border-radius: 2px;
  background: #1a1814;
  opacity: 0.25;
  transition: height 0.3s, opacity 0.3s;
}
.btn-pp-bar.is-active {
  opacity: 0.7;
  animation: btn-pp-dance var(--dur, 0.8s) ease-in-out infinite alternate;
}
@keyframes btn-pp-dance {
  from { height: var(--min, 4px); }
  to   { height: var(--max, 28px); }
}
@media (prefers-reduced-motion: reduce) {
  .btn-pp.is-playing .btn-pp-ring,
  .btn-pp-bar.is-active { animation: none; }
}
<div class="btn-pp-surface">
  <div class="btn-pp-area">
    <button class="btn-pp" aria-label="Play">
      <span class="btn-pp-ring" aria-hidden="true"></span>
      <span class="btn-pp-play" aria-hidden="true">▶</span>
      <span class="btn-pp-pause" aria-hidden="true"><span></span><span></span></span>
    </button>
    <div class="btn-pp-info">
      <div class="btn-pp-meta">
        <span class="btn-pp-title">Midnight Drive</span>
        <span class="btn-pp-sub">3:42 · Synthwave</span>
      </div>
      <span class="btn-pp-wave" aria-hidden="true"></span>
    </div>
  </div>
</div>
document.querySelectorAll('.btn-pp-area').forEach(function (area) {
  var btn = area.querySelector('.btn-pp');
  var wave = area.querySelector('.btn-pp-wave');
  var playing = false;
  var barCount = 18;
  var heights = [];
  for (var i = 0; i < barCount; i++) {
    var mid = barCount / 2;
    var d = Math.abs(i - mid) / mid;
    var h = { min: 3 + d * 3, max: 6 + (1 - d * d) * 26 };
    heights.push(h);
    var bar = document.createElement('span');
    bar.className = 'btn-pp-bar';
    bar.style.height = h.min + 'px';
    bar.style.setProperty('--min', h.min + 'px');
    bar.style.setProperty('--max', h.max + 'px');
    bar.style.setProperty('--dur', (0.4 + Math.random() * 0.7).toFixed(2) + 's');
    bar.style.animationDelay = (Math.random() * 0.5).toFixed(2) + 's';
    wave.appendChild(bar);
  }
  btn.addEventListener('click', function () {
    playing = !playing;
    btn.classList.toggle('is-playing', playing);
    btn.setAttribute('aria-label', playing ? 'Pause' : 'Play');
    var bars = wave.querySelectorAll('.btn-pp-bar');
    bars.forEach(function (b, i) {
      if (playing) {
        b.classList.add('is-active');
        b.style.height = '';
      } else {
        b.classList.remove('is-active');
        b.style.height = heights[i].min + 'px';
      }
    });
  });
});
06 / 43
Subscribe Confetti
CSS + JS NEW
The border fills upward with a purple gradient, the label swaps to a checkmark, then twenty-four confetti pieces in six colors burst outward in a celebratory fan.
Try it
.btn-sub {
  position: relative;
  min-width: 180px;
  padding: 14px 30px;
  border: 2px solid #7c3aed;
  border-radius: 12px;
  background: transparent;
  color: #7c3aed;
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  overflow: hidden;
  user-select: none;
  transition: transform 0.2s;
}
.btn-sub-fill {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%);
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform 0.4s cubic-bezier(.77,0,.18,1);
}
.btn-sub-text {
  position: relative;
  z-index: 1;
  transition: color 0.2s 0.15s, opacity 0.2s;
}
.btn-sub-check {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.3s 0.2s, transform 0.3s 0.2s;
}
.btn-sub.is-subscribed { pointer-events: none; transform: scale(0.98); }
.btn-sub.is-subscribed .btn-sub-fill { transform: scaleY(1); }
.btn-sub.is-subscribed .btn-sub-text { opacity: 0; color: #fff; }
.btn-sub.is-subscribed .btn-sub-check { opacity: 1; transform: translateY(0); }
.btn-sub-confetti {
  position: absolute;
  width: var(--w, 6px);
  height: var(--h, 8px);
  background: var(--c, #7c3aed);
  pointer-events: none;
  z-index: 20;
  animation: btn-sub-fall 0.9s cubic-bezier(.22,1,.36,1) forwards;
}
@keyframes btn-sub-fall {
  0%   { transform: translate(0,0) rotate(0deg) scale(1); opacity: 1; }
  100% { transform: translate(var(--tx), var(--ty)) rotate(var(--r)) scale(0.5); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-sub-fill, .btn-sub-text, .btn-sub-check { transition: none; }
  .btn-sub-confetti { animation: none; }
}
<button class="btn-sub">
  <span class="btn-sub-fill" aria-hidden="true"></span>
  <span class="btn-sub-text">Subscribe Now</span>
  <span class="btn-sub-check">✦&nbsp;Subscribed!</span>
</button>
document.querySelectorAll('.btn-sub').forEach(function (btn) {
  var done = false;
  var colors = ['#7c3aed', '#a855f7', '#ec4899', '#f59e0b', '#10b981', '#3b82f6'];
  btn.addEventListener('click', function () {
    if (done) return;
    done = true;
    btn.classList.add('is-subscribed');
    var w = btn.offsetWidth;
    var h = btn.offsetHeight;
    for (var i = 0; i < 24; i++) {
      var p = document.createElement('span');
      p.className = 'btn-sub-confetti';
      var angle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI * 1.6;
      var dist = 40 + Math.random() * 70;
      p.style.left = (w / 2 + (Math.random() - 0.5) * w * 0.6) + 'px';
      p.style.top = (h / 2) + 'px';
      p.style.setProperty('--w', (4 + Math.random() * 6) + 'px');
      p.style.setProperty('--h', (5 + Math.random() * 9) + 'px');
      p.style.setProperty('--c', colors[Math.floor(Math.random() * colors.length)]);
      p.style.setProperty('--tx', (Math.cos(angle) * dist) + 'px');
      p.style.setProperty('--ty', (Math.sin(angle) * dist) + 'px');
      p.style.setProperty('--r', (Math.random() * 360 - 180) + 'deg');
      p.style.animationDelay = (Math.random() * 0.15) + 's';
      p.style.borderRadius = Math.random() > 0.5 ? '50%' : '2px';
      btn.appendChild(p);
      p.addEventListener('animationend', function () { this.remove(); });
    }
  });
});
07 / 43
Magnetic Mercury
CSS + JS NEW
A liquid-metal pill with a polished radial sheen. Click anywhere on the surface and a bright ripple expands from the exact point of contact, like mercury catching the light.
Try it
.btn-merc {
  position: relative;
  padding: 16px 38px;
  border: none;
  border-radius: 60px;
  background: linear-gradient(135deg, #e8e0d5 0%, #c8bfb0 50%, #a89f94 100%);
  color: #0c0c0f;
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  cursor: pointer;
  overflow: hidden;
  user-select: none;
  box-shadow:
    0 2px 20px rgba(200,191,176,0.15),
    inset 0 1px 0 rgba(255,255,255,0.4),
    inset 0 -1px 0 rgba(0,0,0,0.15);
  transition: transform 0.15s cubic-bezier(.34,1.56,.64,1), box-shadow 0.3s ease;
}
.btn-merc::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(ellipse 80% 60% at 50% -20%, rgba(255,255,255,0.6) 0%, transparent 60%);
  pointer-events: none;
}
.btn-merc:hover {
  transform: scale(1.04) translateY(-2px);
  box-shadow:
    0 8px 40px rgba(200,191,176,0.25),
    inset 0 1px 0 rgba(255,255,255,0.5),
    inset 0 -1px 0 rgba(0,0,0,0.1);
}
.btn-merc:active { transform: scale(0.94); }
.btn-merc-ripple {
  position: absolute;
  border-radius: 50%;
  transform: scale(0);
  background: rgba(255,255,255,0.4);
  pointer-events: none;
  animation: btn-merc-ripple 0.7s cubic-bezier(.22,1,.36,1) forwards;
}
@keyframes btn-merc-ripple {
  to { transform: scale(4); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-merc-ripple { animation: none; display: none; }
}
<button class="btn-merc">Initiate</button>
document.querySelectorAll('.btn-merc').forEach(function (btn) {
  btn.addEventListener('click', function (e) {
    var r = btn.getBoundingClientRect();
    var size = Math.max(r.width, r.height) * 1.5;
    var ripple = document.createElement('span');
    ripple.className = 'btn-merc-ripple';
    ripple.style.width = size + 'px';
    ripple.style.height = size + 'px';
    ripple.style.left = (e.clientX - r.left - size / 2) + 'px';
    ripple.style.top = (e.clientY - r.top - size / 2) + 'px';
    btn.appendChild(ripple);
    ripple.addEventListener('animationend', function () { this.remove(); });
  });
});
08 / 43
Brutalist Glitch
CSS + JS NEW
A raw outlined button that fills hard on hover. Click triggers a chromatic-aberration glitch — the label tears into red and cyan layers with a violent shake before snapping back.
Try it
.btn-glitch {
  position: relative;
  padding: 14px 36px;
  border: 2px solid #fff;
  background: transparent;
  color: #fff;
  font-family: "Arial Narrow", Arial, sans-serif;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 3px;
  text-transform: uppercase;
  cursor: pointer;
  overflow: hidden;
  user-select: none;
  transition: color 0.1s;
}
.btn-glitch-fill {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: #fff;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.15s cubic-bezier(.77,0,.18,1);
}
.btn-glitch-label { position: relative; z-index: 1; }
.btn-glitch:hover { color: #0c0c0f; }
.btn-glitch:hover .btn-glitch-fill { transform: scaleX(1); }
.btn-glitch.is-glitching { animation: btn-glitch-shake 0.4s cubic-bezier(.36,.07,.19,.97) both; }
.btn-glitch.is-glitching::before,
.btn-glitch.is-glitching::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Arial Narrow", Arial, sans-serif;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 3px;
  overflow: hidden;
}
.btn-glitch.is-glitching::before {
  color: #ff0040;
  clip-path: polygon(0 0, 100% 0, 100% 45%, 0 45%);
  animation: btn-glitch-l1 0.4s steps(2) forwards;
}
.btn-glitch.is-glitching::after {
  color: #00fff9;
  clip-path: polygon(0 55%, 100% 55%, 100% 100%, 0 100%);
  animation: btn-glitch-l2 0.4s steps(2) forwards;
}
@keyframes btn-glitch-shake {
  0%,100% { transform: translate(0); }
  20% { transform: translate(-3px, 1px) skewX(-3deg); }
  40% { transform: translate(3px, -1px) skewX(3deg); }
  60% { transform: translate(-2px, 2px); }
  80% { transform: translate(2px, -2px); }
}
@keyframes btn-glitch-l1 {
  0% { transform: translate(-4px, -2px); opacity: 1; }
  50% { transform: translate(4px, 2px); opacity: 0.8; }
  100% { transform: translate(0); opacity: 0; }
}
@keyframes btn-glitch-l2 {
  0% { transform: translate(4px, 2px); opacity: 1; }
  50% { transform: translate(-4px, -2px); opacity: 0.8; }
  100% { transform: translate(0); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-glitch.is-glitching { animation: none; }
  .btn-glitch.is-glitching::before,
  .btn-glitch.is-glitching::after { animation: none; opacity: 0; }
}
<button class="btn-glitch" data-text="SUBMIT">
  <span class="btn-glitch-fill" aria-hidden="true"></span>
  <span class="btn-glitch-label">SUBMIT</span>
</button>
document.querySelectorAll('.btn-glitch').forEach(function (btn) {
  btn.addEventListener('click', function () {
    btn.classList.add('is-glitching');
    setTimeout(function () { btn.classList.remove('is-glitching'); }, 420);
  });
});
09 / 43
Neon Noir
CSS + JS NEW
A cyberpunk terminal button glowing faint cyan. Click detonates a plasma burst — an expanding ring, a radial bloom, and ten sparks flung from the point of contact.
Try it
.btn-neon {
  position: relative;
  padding: 16px 36px;
  border: 1px solid rgba(0,255,249,0.3);
  background: rgba(0,255,249,0.04);
  color: #00fff9;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 3px;
  text-transform: uppercase;
  cursor: pointer;
  overflow: hidden;
  user-select: none;
  box-shadow: 0 0 20px rgba(0,255,249,0.05), inset 0 0 20px rgba(0,255,249,0.02);
  transition: border-color 0.3s, background 0.3s, box-shadow 0.3s, color 0.3s;
}
.btn-neon:hover {
  border-color: rgba(0,255,249,0.8);
  background: rgba(0,255,249,0.08);
  box-shadow: 0 0 30px rgba(0,255,249,0.2), inset 0 0 30px rgba(0,255,249,0.05);
  color: #fff;
}
.btn-neon-plasma,
.btn-neon-ring {
  position: absolute;
  border-radius: 50%;
  transform: scale(0);
  pointer-events: none;
}
.btn-neon-plasma {
  background: radial-gradient(circle, rgba(0,255,249,0.8) 0%, rgba(0,120,255,0.4) 40%, transparent 70%);
  mix-blend-mode: screen;
  animation: btn-neon-burst 0.7s cubic-bezier(.22,1,.36,1) forwards;
}
.btn-neon-ring {
  border: 2px solid rgba(0,255,249,0.6);
  animation: btn-neon-ring 0.7s cubic-bezier(.22,1,.36,1) forwards;
}
@keyframes btn-neon-burst {
  0% { transform: scale(0); opacity: 1; }
  60% { opacity: 0.6; }
  100% { transform: scale(5); opacity: 0; }
}
@keyframes btn-neon-ring {
  0% { transform: scale(0); opacity: 1; }
  100% { transform: scale(4); opacity: 0; }
}
.btn-neon-spark {
  position: absolute;
  width: 2px;
  height: 2px;
  border-radius: 50%;
  background: #00fff9;
  pointer-events: none;
  animation: btn-neon-spark 0.6s ease-out forwards;
}
@keyframes btn-neon-spark {
  0% { transform: translate(0,0) scale(1); opacity: 1; }
  100% { transform: translate(var(--sx), var(--sy)) scale(0); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-neon-plasma, .btn-neon-ring, .btn-neon-spark { animation: none; display: none; }
}
<button class="btn-neon">Connect</button>
document.querySelectorAll('.btn-neon').forEach(function (btn) {
  btn.addEventListener('click', function (e) {
    var r = btn.getBoundingClientRect();
    var x = e.clientX - r.left;
    var y = e.clientY - r.top;
    var size = Math.max(r.width, r.height) * 0.8;
    ['btn-neon-plasma', 'btn-neon-ring'].forEach(function (cls) {
      var el = document.createElement('div');
      el.className = cls;
      el.style.width = size + 'px';
      el.style.height = size + 'px';
      el.style.left = (x - size / 2) + 'px';
      el.style.top = (y - size / 2) + 'px';
      btn.appendChild(el);
      el.addEventListener('animationend', function () { this.remove(); });
    });
    for (var i = 0; i < 10; i++) {
      var s = document.createElement('span');
      s.className = 'btn-neon-spark';
      var angle = (i / 10) * Math.PI * 2;
      var dist = 30 + Math.random() * 50;
      s.style.left = x + 'px';
      s.style.top = y + 'px';
      s.style.setProperty('--sx', (Math.cos(angle) * dist) + 'px');
      s.style.setProperty('--sy', (Math.sin(angle) * dist) + 'px');
      s.style.animationDelay = (Math.random() * 0.1) + 's';
      btn.appendChild(s);
      s.addEventListener('animationend', function () { this.remove(); });
    }
  });
});
10 / 43
Clay Press
CSS + JS NEW
A chunky claymorphism button with a thick 3D base shadow. Press it and the whole shape squishes down and stretches wide, with a soft white bloom blossoming from the click point.
Try it
.btn-clay {
  position: relative;
  padding: 18px 40px;
  border: none;
  border-radius: 20px;
  background: #2ecc71;
  color: #fff;
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  overflow: hidden;
  user-select: none;
  box-shadow:
    0 6px 0 #1a7a43,
    0 10px 20px rgba(0,0,0,0.3),
    inset 0 2px 4px rgba(255,255,255,0.2);
  transition: transform 0.2s cubic-bezier(.34,1.56,.64,1), box-shadow 0.2s ease, filter 0.2s ease;
}
.btn-clay:hover {
  transform: translateY(-3px) scale(1.03);
  box-shadow:
    0 9px 0 #1a7a43,
    0 16px 30px rgba(0,0,0,0.35),
    inset 0 2px 4px rgba(255,255,255,0.2);
}
.btn-clay:active {
  transform: translateY(4px) scale(0.95) scaleX(1.06);
  box-shadow:
    0 2px 0 #1a7a43,
    0 4px 10px rgba(0,0,0,0.2),
    inset 0 2px 8px rgba(0,0,0,0.2);
  filter: saturate(1.4) brightness(0.95);
}
.btn-clay-blob {
  position: absolute;
  border-radius: 50%;
  transform: scale(0);
  background: rgba(255,255,255,0.25);
  pointer-events: none;
  animation: btn-clay-bloom 0.5s cubic-bezier(.22,1,.36,1) forwards;
}
@keyframes btn-clay-bloom {
  0% { transform: scale(0); opacity: 0.8; }
  100% { transform: scale(3); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-clay-blob { animation: none; display: none; }
}
<button class="btn-clay">Confirm</button>
document.querySelectorAll('.btn-clay').forEach(function (btn) {
  btn.addEventListener('click', function (e) {
    var r = btn.getBoundingClientRect();
    var size = Math.max(r.width, r.height);
    var blob = document.createElement('div');
    blob.className = 'btn-clay-blob';
    blob.style.width = size + 'px';
    blob.style.height = size + 'px';
    blob.style.left = (e.clientX - r.left - size / 2) + 'px';
    blob.style.top = (e.clientY - r.top - size / 2) + 'px';
    btn.appendChild(blob);
    blob.addEventListener('animationend', function () { this.remove(); });
  });
});
11 / 43
Editorial Ink
CSS + JS NEW
A serif, italic button with a thin frame and a hairline underline that draws in on hover. Press and hold and ink floods the button from your finger; release and it retreats.
Try it
.btn-ink {
  position: relative;
  padding: 14px 32px;
  border: none;
  background: transparent;
  color: #f5f0e8;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 16px;
  font-weight: 700;
  font-style: italic;
  letter-spacing: -0.5px;
  cursor: pointer;
  overflow: hidden;
  user-select: none;
}
.btn-ink::before {
  content: '';
  position: absolute;
  inset: 0;
  border: 1.5px solid rgba(245,240,232,0.2);
  transition: border-color 0.3s;
}
.btn-ink:hover::before { border-color: rgba(245,240,232,0.5); }
.btn-ink-underline {
  position: absolute;
  left: 32px;
  right: 32px;
  bottom: 10px;
  height: 1px;
  background: #f5f0e8;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s cubic-bezier(.77,0,.18,1);
}
.btn-ink:hover .btn-ink-underline { transform: scaleX(1); }
.btn-ink-fill {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: #f5f0e8;
  clip-path: circle(0% at var(--ox, 50%) var(--oy, 50%));
}
.btn-ink-label { position: relative; z-index: 1; }
.btn-ink.is-spreading .btn-ink-fill {
  clip-path: circle(150% at var(--ox, 50%) var(--oy, 50%));
  transition: clip-path 0.6s cubic-bezier(.4,0,.2,1);
}
.btn-ink.is-spreading .btn-ink-label { color: #0c0c0f; transition: color 0.2s 0.15s; }
.btn-ink.is-retreating .btn-ink-fill {
  clip-path: circle(0% at var(--ox, 50%) var(--oy, 50%));
  transition: clip-path 0.5s cubic-bezier(.4,0,.2,1) 0.05s;
}
@media (prefers-reduced-motion: reduce) {
  .btn-ink-fill, .btn-ink-underline { transition: none; }
}
<button class="btn-ink">
  <span class="btn-ink-fill" aria-hidden="true"></span>
  <span class="btn-ink-underline" aria-hidden="true"></span>
  <span class="btn-ink-label">Discover</span>
</button>
document.querySelectorAll('.btn-ink').forEach(function (btn) {
  function retreat() {
    btn.classList.remove('is-spreading');
    btn.classList.add('is-retreating');
    setTimeout(function () { btn.classList.remove('is-retreating'); }, 600);
  }
  btn.addEventListener('mousedown', function (e) {
    var r = btn.getBoundingClientRect();
    btn.style.setProperty('--ox', ((e.clientX - r.left) / r.width * 100) + '%');
    btn.style.setProperty('--oy', ((e.clientY - r.top) / r.height * 100) + '%');
    btn.classList.remove('is-retreating');
    btn.classList.add('is-spreading');
  });
  btn.addEventListener('mouseup', retreat);
  btn.addEventListener('mouseleave', function () {
    if (btn.classList.contains('is-spreading')) retreat();
  });
});
12 / 43
Particle Burst
CSS + JS NEW
A warm, dark editorial button. Click and eighteen triangular shards explode outward in a full circle — each a random size, colour, and spin — like struck flint throwing sparks.
Try it
.btn-shard-surface {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 56px;
  background: #0c0c0f;
  border-radius: 16px;
}
.btn-shard {
  position: relative;
  padding: 16px 44px;
  border: 1px solid rgba(232,213,176,0.2);
  background: linear-gradient(135deg, #1a1206 0%, #2c1f0a 100%);
  color: #e8d5b0;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 18px;
  font-weight: 400;
  font-style: italic;
  letter-spacing: 1px;
  cursor: pointer;
  user-select: none;
  transition: border-color 0.3s, background 0.3s, transform 0.1s;
}
.btn-shard:hover {
  border-color: rgba(232,213,176,0.5);
  background: linear-gradient(135deg, #221a0a 0%, #3a2810 100%);
}
.btn-shard:active { transform: scale(0.97); }
.btn-shard-label { position: relative; z-index: 1; }
.btn-shard-canvas {
  position: absolute;
  inset: -60px;
  z-index: 10;
  pointer-events: none;
}
.btn-shard-piece {
  position: absolute;
  width: var(--w, 6px);
  height: var(--h, 6px);
  background: var(--c, #e8d5b0);
  clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
  animation: btn-shard-fly 0.7s cubic-bezier(.22,1,.36,1) forwards;
}
@keyframes btn-shard-fly {
  0% { transform: translate(0,0) rotate(0deg) scale(1); opacity: 1; }
  100% { transform: translate(var(--tx,0px), var(--ty,-60px)) rotate(var(--r,360deg)) scale(0); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-shard-piece { animation: none; display: none; }
}
<div class="btn-shard-surface">
  <button class="btn-shard">
    <span class="btn-shard-canvas" aria-hidden="true"></span>
    <span class="btn-shard-label">Illuminate</span>
  </button>
</div>
document.querySelectorAll('.btn-shard').forEach(function (btn) {
  var colors = ['#e8d5b0', '#f5c842', '#ff8c42', '#e8d5b0', '#fff8e7'];
  btn.addEventListener('click', function (e) {
    var canvas = btn.querySelector('.btn-shard-canvas');
    var cr = canvas.getBoundingClientRect();
    var cx = e.clientX - cr.left;
    var cy = e.clientY - cr.top;
    for (var i = 0; i < 18; i++) {
      var piece = document.createElement('span');
      piece.className = 'btn-shard-piece';
      var angle = (i / 18) * Math.PI * 2 + (Math.random() - 0.5) * 0.5;
      var dist = 40 + Math.random() * 70;
      var size = 4 + Math.random() * 8;
      piece.style.left = cx + 'px';
      piece.style.top = cy + 'px';
      piece.style.setProperty('--tx', (Math.cos(angle) * dist) + 'px');
      piece.style.setProperty('--ty', (Math.sin(angle) * dist) + 'px');
      piece.style.setProperty('--r', (Math.random() * 360 - 180) + 'deg');
      piece.style.setProperty('--w', size + 'px');
      piece.style.setProperty('--h', size + 'px');
      piece.style.setProperty('--c', colors[Math.floor(Math.random() * colors.length)]);
      piece.style.animationDelay = (Math.random() * 0.1) + 's';
      canvas.appendChild(piece);
      piece.addEventListener('animationend', function () { this.remove(); });
    }
  });
});
Advertisement
13 / 43
Brushed Brass
Pure CSS
Letterpress brass plate with engraved label and steel rivets at the corners. Hover deepens the bevel; click presses the plate into the surface.
Try it
.btn-brass {
  position: relative;
  padding: 14px 36px;
  border: none;
  border-radius: 4px;
  background:
    repeating-linear-gradient(
      90deg,
      rgba(255,232,180,0.12) 0 1px,
      transparent 1px 3px
    ),
    linear-gradient(180deg, #d8b66e 0%, #c9a15e 50%, #a07f3a 100%);
  color: #2a1d0e;
  font-family: ui-serif, Georgia, serif;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: auto auto;
  gap: 0 24px;
  align-items: center;
  box-shadow:
    inset 0 1px 0 rgba(255,232,180,0.6),
    inset 0 -2px 0 rgba(58,36,16,0.4),
    0 4px 8px rgba(0,0,0,0.3);
  text-shadow: 0 1px 0 rgba(255,232,180,0.5);
  transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.18s ease;
}
.btn-brass-rivet {
  width: 6px; height: 6px; border-radius: 50%;
  background: radial-gradient(circle at 30% 30%, #f0e8d8, #6a5a30 70%);
  box-shadow: inset 0 -1px 1px rgba(0,0,0,0.4);
}
.btn-brass-label {
  grid-column: span 1;
  grid-row: 1 / span 2;
  text-align: center;
  border-top: 1px solid rgba(58,36,16,0.4);
  border-bottom: 1px solid rgba(58,36,16,0.4);
  padding: 4px 0;
}
.btn-brass:hover {
  filter: brightness(1.06);
  box-shadow:
    inset 0 1px 0 rgba(255,232,180,0.7),
    inset 0 -2px 0 rgba(58,36,16,0.5),
    0 6px 12px rgba(0,0,0,0.35);
}
.btn-brass:active {
  transform: translateY(2px);
  box-shadow:
    inset 0 2px 4px rgba(58,36,16,0.4),
    0 1px 2px rgba(0,0,0,0.2);
}
<button class="btn-brass">
  <span class="btn-brass-rivet" aria-hidden="true"></span>
  <span class="btn-brass-rivet" aria-hidden="true"></span>
  <span class="btn-brass-label">Engage</span>
  <span class="btn-brass-rivet" aria-hidden="true"></span>
  <span class="btn-brass-rivet" aria-hidden="true"></span>
</button>
14 / 43
Paper Crane
Pure CSS
Washi paper with a hand-stamped red ink seal in the corner. The bottom-right corner is folded like an origami crease; hover lifts the whole card like a postcard.
Try it
.btn-crane {
  position: relative;
  padding: 14px 44px 14px 28px;
  border: 1px solid #d4c8a3;
  border-radius: 2px;
  background: #f5e8c4;
  color: #3a2010;
  font-family: ui-serif, Georgia, serif;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.06em;
  cursor: pointer;
  overflow: hidden;
  transition: transform 0.25s cubic-bezier(.3,1,.3,1), box-shadow 0.25s ease;
  box-shadow: 0 2px 0 rgba(170,140,80,0.4), 0 4px 10px rgba(0,0,0,0.12);
}
.btn-crane-text { position: relative; z-index: 1; }
.btn-crane-seal {
  position: absolute;
  top: 50%; right: 10px;
  transform: translateY(-50%);
  width: 20px; height: 20px;
  background: #a83232;
  color: #f5e8c4;
  font-family: ui-serif, Georgia, serif;
  font-size: 12px; font-weight: 700;
  display: grid; place-items: center;
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.18);
}
.btn-crane-fold {
  position: absolute;
  right: 0; bottom: 0;
  width: 14px; height: 14px;
  background: linear-gradient(135deg, transparent 50%, #d4c8a3 50%);
}
.btn-crane:hover {
  transform: translateY(-3px) rotate(-0.6deg);
  box-shadow: 0 4px 0 rgba(170,140,80,0.4), 0 8px 16px rgba(0,0,0,0.18);
}
<button class="btn-crane">
  <span class="btn-crane-text">Send Wish</span>
  <span class="btn-crane-seal" aria-hidden="true">福</span>
  <span class="btn-crane-fold" aria-hidden="true"></span>
</button>
15 / 43
Frosted Ice
Pure CSS
Glacier blue with backdrop-filter refraction and noisy frost grain on top. Hover melts the surface to a brighter, glossier ice.
Try it
.btn-ice {
  position: relative;
  padding: 14px 30px;
  border: 1px solid rgba(168,224,240,0.5);
  border-radius: 12px;
  background: linear-gradient(135deg, rgba(168,224,240,0.55), rgba(93,158,176,0.65));
  color: #0a3a4a;
  font-family: ui-sans-serif, system-ui;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
  overflow: hidden;
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  backdrop-filter: blur(8px) saturate(140%);
  box-shadow: 0 8px 22px -8px rgba(15,80,100,0.4), inset 0 1px 0 rgba(255,255,255,0.6);
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.btn-ice-frost {
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse at 30% 20%, rgba(255,255,255,0.55), transparent 60%),
    repeating-linear-gradient(45deg, rgba(255,255,255,0.04) 0 2px, transparent 2px 4px);
  pointer-events: none;
}
.btn-ice-glint {
  position: absolute; top: -50%; left: -30%;
  width: 30%; height: 200%;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.7), transparent);
  transform: rotate(20deg);
  transition: left 0.5s cubic-bezier(.3,1,.3,1);
  pointer-events: none;
}
.btn-ice-text { position: relative; z-index: 1; }
.btn-ice:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 28px -8px rgba(15,80,100,0.5), inset 0 1px 0 rgba(255,255,255,0.7);
}
.btn-ice:hover .btn-ice-glint { left: 130%; }
<button class="btn-ice">
  <span class="btn-ice-frost" aria-hidden="true"></span>
  <span class="btn-ice-glint" aria-hidden="true"></span>
  <span class="btn-ice-text">Cool Down</span>
</button>
16 / 43
Typewriter Key
Pure CSS
Concave ivory keycap with black ring and serif letter. Click depresses the key 4px with a soft mechanical thud shadow.
Try it
.btn-key {
  display: inline-block;
  padding: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  perspective: 200px;
}
.btn-key-cap {
  display: inline-flex; align-items: center; justify-content: center;
  width: 130px; height: 56px;
  background: radial-gradient(ellipse at 50% 30%, #fff8e8 0%, #f0e8d8 60%, #d4c8a8 100%);
  border: 3px solid #1a1a1a;
  border-radius: 8px;
  box-shadow:
    inset 0 -8px 12px rgba(0,0,0,0.15),
    inset 0 2px 4px rgba(255,255,255,0.6),
    0 5px 0 #1a1a1a,
    0 8px 14px rgba(0,0,0,0.25);
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}
.btn-key-letter {
  font-family: ui-serif, 'Courier New', Georgia, monospace;
  font-size: 13px; font-weight: 800;
  letter-spacing: 0.16em;
  color: #1a1a1a;
}
.btn-key:hover .btn-key-cap {
  transform: translateY(-1px);
}
.btn-key:active .btn-key-cap {
  transform: translateY(4px);
  box-shadow:
    inset 0 -2px 6px rgba(0,0,0,0.25),
    inset 0 2px 4px rgba(255,255,255,0.4),
    0 1px 0 #1a1a1a,
    0 2px 4px rgba(0,0,0,0.2);
}
<button class="btn-key">
  <span class="btn-key-cap">
    <span class="btn-key-letter">RETURN</span>
  </span>
</button>
17 / 43
Lacquered Wood
Pure CSS
Walnut grain block with brass corner brackets and gilt label. Hover deepens the lacquer sheen; the brass catches a tilt of light.
Try it
.btn-wood {
  position: relative;
  padding: 14px 36px;
  border: none;
  border-radius: 3px;
  background:
    repeating-linear-gradient(
      90deg,
      rgba(0,0,0,0.06) 0 1px,
      transparent 1px 4px
    ),
    repeating-linear-gradient(
      0deg,
      rgba(0,0,0,0.04) 0 6px,
      transparent 6px 14px
    ),
    linear-gradient(135deg, #6a4628 0%, #5a3a20 50%, #3a2410 100%);
  color: #c9a15e;
  font-family: ui-serif, Georgia, serif;
  font-size: 13px; font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(201,161,94,0.2),
    0 4px 14px rgba(0,0,0,0.4);
  transition: filter 0.2s ease, transform 0.18s ease;
}
.btn-wood-corner {
  position: absolute;
  width: 12px; height: 12px;
  border: 2px solid #c9a15e;
  background: #c9a15e22;
}
.btn-wood-corner-tl { top: 4px;    left: 4px;    border-right: none;  border-bottom: none; }
.btn-wood-corner-tr { top: 4px;    right: 4px;   border-left: none;   border-bottom: none; }
.btn-wood-corner-bl { bottom: 4px; left: 4px;    border-right: none;  border-top: none; }
.btn-wood-corner-br { bottom: 4px; right: 4px;   border-left: none;   border-top: none; }
.btn-wood-text { position: relative; z-index: 1; }
.btn-wood:hover {
  filter: brightness(1.12);
  transform: translateY(-1px);
}
<button class="btn-wood">
  <span class="btn-wood-corner btn-wood-corner-tl" aria-hidden="true"></span>
  <span class="btn-wood-corner btn-wood-corner-tr" aria-hidden="true"></span>
  <span class="btn-wood-corner btn-wood-corner-bl" aria-hidden="true"></span>
  <span class="btn-wood-corner btn-wood-corner-br" aria-hidden="true"></span>
  <span class="btn-wood-text">Reserve</span>
</button>
18 / 43
Gel Capsule
Pure CSS
Pill-shaped pharma capsule split half clinical-blue, half pearl-white, with a chunky black band carrying the dose label. Hover brightens both halves.
Try it
.btn-pill {
  position: relative;
  width: 220px; height: 48px;
  border: none;
  border-radius: 999px;
  background: transparent;
  cursor: pointer;
  isolation: isolate;
  filter: drop-shadow(0 4px 10px rgba(58,120,201,0.3));
  transition: transform 0.18s ease, filter 0.18s ease;
}
.btn-pill-half {
  position: absolute; top: 0; bottom: 0; width: 50%;
}
.btn-pill-l {
  left: 0;
  background: linear-gradient(180deg, #5a96e0 0%, #3a78c9 60%, #2a5a99 100%);
  border-radius: 999px 0 0 999px;
  box-shadow: inset 4px 0 6px rgba(0,0,0,0.1);
}
.btn-pill-l::after {
  content: '';
  position: absolute; top: 5px; left: 14px; right: 4px; height: 35%;
  background: linear-gradient(180deg, rgba(255,255,255,0.55), transparent);
  border-radius: 999px 0 0 0;
}
.btn-pill-r {
  right: 0;
  background: linear-gradient(180deg, #ffffff 0%, #e8edf2 60%, #c8d2dc 100%);
  border-radius: 0 999px 999px 0;
  box-shadow: inset -4px 0 6px rgba(0,0,0,0.08);
}
.btn-pill-r::after {
  content: '';
  position: absolute; top: 5px; left: 4px; right: 14px; height: 35%;
  background: linear-gradient(180deg, rgba(255,255,255,0.9), transparent);
  border-radius: 0 999px 0 0;
}
.btn-pill-band {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 86%; height: 22px;
  background: #0e0e1a;
  border-radius: 4px;
  display: grid; place-items: center;
  z-index: 1;
}
.btn-pill-text {
  font-family: ui-monospace, monospace;
  font-size: 11px; font-weight: 800;
  color: #fdfcfa;
  letter-spacing: 0.18em;
}
.btn-pill:hover { transform: translateY(-2px); filter: drop-shadow(0 6px 14px rgba(58,120,201,0.45)); }
<button class="btn-pill">
  <span class="btn-pill-half btn-pill-l" aria-hidden="true"></span>
  <span class="btn-pill-half btn-pill-r" aria-hidden="true"></span>
  <span class="btn-pill-band">
    <span class="btn-pill-text">PRESCRIBE 500 MG</span>
  </span>
</button>
19 / 43
Copper Coin
Pure CSS
Aged copper disc with verdigris patina blooming around the rim. Hover spins the patina conically.
Try it
@property --btn-copper-angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
.btn-copper {
  position: relative;
  width: 96px; height: 96px;
  border: none; border-radius: 50%;
  background:
    radial-gradient(circle at 35% 30%, #e8b878 0%, #b87333 55%, #6a3f15 100%);
  color: #3a2410;
  font-family: ui-serif, Georgia, serif;
  font-size: 12px; font-weight: 800;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
  isolation: isolate;
  box-shadow:
    inset 0 -3px 6px rgba(0,0,0,0.4),
    inset 0 2px 3px rgba(255,255,255,0.4),
    0 6px 14px rgba(0,0,0,0.3);
  --btn-copper-angle: 0deg;
  transition: --btn-copper-angle 4s linear, transform 0.18s ease;
}
.btn-copper-rim {
  position: absolute; inset: 4px;
  border-radius: 50%;
  background: conic-gradient(from var(--btn-copper-angle),
    transparent 0deg,
    #5d8a73aa 30deg,
    transparent 80deg,
    #88a896aa 200deg,
    transparent 260deg,
    #5d8a73aa 320deg,
    transparent 360deg);
  filter: blur(2px);
  pointer-events: none;
  -webkit-mask: radial-gradient(circle, transparent 60%, #000 70%);
          mask: radial-gradient(circle, transparent 60%, #000 70%);
}
.btn-copper-text { position: relative; z-index: 1; text-shadow: 0 1px 0 rgba(255,232,180,0.4); }
.btn-copper:hover {
  --btn-copper-angle: 360deg;
  transform: scale(1.04);
}
<button class="btn-copper">
  <span class="btn-copper-rim" aria-hidden="true"></span>
  <span class="btn-copper-text">Mint</span>
</button>
20 / 43
Origami Fortune
Pure CSS
Triangular fold of cream paper with a single ink-red strip. Hover unfolds slightly to reveal the fortune underneath.
Try it
.btn-fortune {
  position: relative;
  padding: 16px 36px;
  border: none;
  background: #f5ead0;
  color: #3a2410;
  font-family: ui-serif, Georgia, serif;
  font-size: 13px; font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
  clip-path: polygon(0 0, 100% 0, 100% 70%, 50% 100%, 0 70%);
  box-shadow: 0 4px 12px rgba(58,36,16,0.25);
  transition: transform 0.25s cubic-bezier(.3,1.2,.3,1);
}
.btn-fortune-fold {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 60%, rgba(58,36,16,0.18) 100%);
  pointer-events: none;
}
.btn-fortune-strip {
  position: absolute;
  bottom: 0; left: 50%;
  width: 4px; height: 32%;
  background: #a83232;
  transform: translateX(-50%);
}
.btn-fortune-text { position: relative; z-index: 1; }
.btn-fortune:hover {
  transform: translateY(-2px) rotate(-2deg);
  clip-path: polygon(0 0, 100% 0, 100% 80%, 50% 100%, 0 80%);
}
<button class="btn-fortune">
  <span class="btn-fortune-fold" aria-hidden="true"></span>
  <span class="btn-fortune-strip" aria-hidden="true"></span>
  <span class="btn-fortune-text">Open</span>
</button>
21 / 43
Holographic Foil
Pure CSS
Iridescent cyan→magenta sheen with a ragged sticker edge. Hover shifts the spectrum across the surface.
Try it
.btn-holo {
  position: relative;
  padding: 13px 28px;
  border: 2px solid #fff;
  border-radius: 6px;
  background:
    linear-gradient(115deg,
      #00d4ff 0%,
      #ff3d8c 30%,
      #3eff7f 55%,
      #fef34c 75%,
      #00d4ff 100%);
  background-size: 240% 100%;
  background-position: 0% 50%;
  color: #1a0033;
  font-family: ui-monospace, monospace;
  font-size: 13px; font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow:
    0 4px 18px rgba(0,212,255,0.35),
    0 4px 18px rgba(255,61,140,0.35),
    inset 0 0 0 1px rgba(255,255,255,0.4);
  transition: background-position 0.5s cubic-bezier(.3,1,.3,1), transform 0.18s;
}
.btn-holo-text {
  background: rgba(255,255,255,0.7);
  padding: 4px 10px;
  border-radius: 3px;
}
.btn-holo:hover {
  background-position: 100% 50%;
  transform: translateY(-2px) scale(1.02);
}
<button class="btn-holo">
  <span class="btn-holo-text">Mint NFT</span>
</button>
22 / 43
Brutalist Slab
Pure CSS
Rough concrete with a safety-yellow stripe and hard 6px offset. Click slams the slab to flush with the page.
Try it
.btn-brutal {
  position: relative;
  padding: 16px 40px;
  border: 3px solid #1a1a1a;
  border-radius: 0;
  background:
    repeating-radial-gradient(circle at 20% 30%, rgba(0,0,0,0.06) 0 1px, transparent 1px 5px),
    repeating-radial-gradient(circle at 80% 70%, rgba(0,0,0,0.05) 0 1px, transparent 1px 7px),
    #8a8a85;
  color: #1a1a1a;
  font-family: ui-monospace, monospace;
  font-size: 14px; font-weight: 900;
  letter-spacing: 0.18em;
  cursor: pointer;
  box-shadow: 6px 6px 0 #1a1a1a;
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}
.btn-brutal-stripe {
  position: absolute; left: 0; right: 0;
  top: -3px; height: 6px;
  background: #f5cb1a;
  border-bottom: 3px solid #1a1a1a;
}
.btn-brutal-text { position: relative; }
.btn-brutal:hover { transform: translate(-2px,-2px); box-shadow: 8px 8px 0 #1a1a1a; }
.btn-brutal:active { transform: translate(6px,6px); box-shadow: 0 0 0 #1a1a1a; }
<button class="btn-brutal">
  <span class="btn-brutal-stripe" aria-hidden="true"></span>
  <span class="btn-brutal-text">SUBMIT</span>
</button>
23 / 43
Mercury Bead
Pure CSS
Quicksilver bead that morphs its border-radius on hover, like surface tension giving way under pressure.
Try it
.btn-mercury {
  padding: 14px 32px;
  border: none;
  border-radius: 50% 14px 50% 14px / 14px 50% 14px 50%;
  background: radial-gradient(circle at 30% 30%, #e0e6ec 0%, #bcc4ce 50%, #5a6470 100%);
  color: #1e242a;
  font-family: ui-sans-serif, system-ui;
  font-size: 14px; font-weight: 700;
  cursor: pointer;
  box-shadow:
    inset 0 -4px 8px rgba(30,36,42,0.4),
    inset 0 2px 4px rgba(255,255,255,0.6),
    0 4px 12px rgba(30,36,42,0.4);
  transition: border-radius 0.5s cubic-bezier(.3,1.2,.3,1), transform 0.3s;
}
.btn-mercury:hover {
  border-radius: 14px 50% 14px 50% / 50% 14px 50% 14px;
  transform: scale(1.04);
}
<button class="btn-mercury">Liquid</button>
24 / 43
Soft Pill
Pure CSS
The Linear / Raycast / Vercel button trend of 2026 — soft gradient pill with a crisp inset highlight, ambient glow, and a tiny shortcut chip on the right. Hover brightens the glow without moving the button.
Try it
.btn-soft {
  --soft-bg-1: #2a2a3a;
  --soft-bg-2: #16161e;
  --soft-glow: #6e7eff;
  position: relative;
  padding: 11px 14px 11px 18px;
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: 999px;
  background: linear-gradient(180deg, var(--soft-bg-1), var(--soft-bg-2));
  color: #f5f5fa;
  font-family: ui-sans-serif, system-ui;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.12),
    inset 0 -1px 0 rgba(0,0,0,0.4),
    0 6px 14px rgba(0,0,0,0.35),
    0 0 0 0 var(--soft-glow);
  transition: box-shadow 0.25s ease, transform 0.12s ease;
}
.btn-soft-icon {
  width: 14px; height: 14px;
  color: var(--soft-glow);
  flex-shrink: 0;
}
.btn-soft-kbd {
  font-family: ui-monospace, monospace;
  font-size: 11px;
  color: #a8a8b8;
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 4px;
  padding: 1px 6px;
  margin-left: 4px;
}
.btn-soft:hover {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.15),
    inset 0 -1px 0 rgba(0,0,0,0.4),
    0 6px 14px rgba(0,0,0,0.35),
    0 0 22px -4px var(--soft-glow);
}
.btn-soft:active { transform: translateY(1px); }
<button class="btn-soft">
  <svg
    class="btn-soft-icon"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2.4"
    stroke-linecap="round"
    stroke-linejoin="round"
    aria-hidden="true"
  >
    <path d="M5 12h14M13 5l7 7-7 7" />
  </svg>
  <span class="btn-soft-text">Continue</span>
  <span class="btn-soft-kbd" aria-hidden="true">⏎</span>
</button>
Advertisement
25 / 43
Masking Tape
Pure CSS
Kraft masking tape with torn perforated edges and a hand-stamped ink label. Hover lifts the corner like a peel.
Try it
.btn-tape {
  position: relative;
  padding: 14px 38px;
  border: none;
  border-radius: 0;
  background:
    repeating-linear-gradient(135deg,
      rgba(0,0,0,0.04) 0 6px,
      transparent 6px 12px),
    #d8b87a;
  color: #2a1d0e;
  font-family: ui-monospace, monospace;
  font-size: 14px; font-weight: 800;
  letter-spacing: 0.32em;
  cursor: pointer;
  /* Torn edges: scalloped left/right via radial-gradient mask */
  -webkit-mask:
    radial-gradient(circle 4px at 0% 50%, transparent 99%, #000 100%),
    radial-gradient(circle 4px at 100% 50%, transparent 99%, #000 100%),
    linear-gradient(#000, #000);
          mask:
    radial-gradient(circle 4px at 0% 50%, transparent 99%, #000 100%),
    radial-gradient(circle 4px at 100% 50%, transparent 99%, #000 100%),
    linear-gradient(#000, #000);
  -webkit-mask-composite: source-over;
          mask-composite: add;
  box-shadow: 0 3px 8px rgba(0,0,0,0.2);
  transition: transform 0.25s cubic-bezier(.3,1,.3,1);
}
.btn-tape:hover {
  transform: rotate(-2deg) translateY(-2px);
}
<button class="btn-tape">
  <span class="btn-tape-text">FRAGILE</span>
</button>
26 / 43
Glass Shard
Pure CSS
Obsidian shard with a faceted clip-path; magenta and cyan refract through the cuts on hover.
Try it
.btn-shard {
  position: relative;
  padding: 14px 36px;
  border: none;
  background:
    linear-gradient(115deg,
      #0e0e1a 0%,
      #ff3d8c 30%,
      #0e0e1a 50%,
      #00d4ff 70%,
      #0e0e1a 100%);
  background-size: 240% 100%;
  background-position: 0% 50%;
  color: #fff;
  font-family: ui-sans-serif, system-ui;
  font-size: 14px; font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  clip-path: polygon(8% 0, 92% 0, 100% 50%, 92% 100%, 8% 100%, 0 50%);
  box-shadow: 0 6px 18px rgba(0,212,255,0.25), 0 6px 18px rgba(255,61,140,0.25);
  transition: background-position 0.6s cubic-bezier(.3,1,.3,1), transform 0.2s;
}
.btn-shard-text { position: relative; }
.btn-shard:hover {
  background-position: 100% 50%;
  transform: translateY(-2px);
}
<button class="btn-shard">
  <span class="btn-shard-text">Activate</span>
</button>
27 / 43
Velvet Ribbon
Pure CSS
Wine-velvet pill with gilt edges; the nap shimmers like brushed fabric on hover.
Try it
.btn-velvet {
  position: relative;
  padding: 13px 36px;
  border: 2px solid #d4a017;
  border-radius: 999px;
  background:
    repeating-linear-gradient(115deg,
      rgba(212,160,23,0.06) 0 1px,
      transparent 1px 4px),
    radial-gradient(ellipse at 50% 30%, #6a2235 0%, #5a1d2c 60%, #3a0e1a 100%);
  color: #f5d678;
  font-family: ui-serif, Georgia, serif;
  font-size: 13px; font-weight: 700;
  letter-spacing: 0.22em;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(245,214,120,0.25),
    inset 0 -8px 18px rgba(0,0,0,0.4),
    0 4px 14px rgba(58,14,26,0.45);
  transition: filter 0.3s ease, transform 0.2s ease;
}
.btn-velvet:hover {
  filter: brightness(1.18) saturate(1.1);
  transform: translateY(-1px);
}
<button class="btn-velvet">
  <span class="btn-velvet-text">RSVP</span>
</button>
28 / 43
LED Segment
Pure CSS
Phosphor-green 7-segment-style display on a pure black panel with a faint scanline overlay.
Try it
.btn-led {
  position: relative;
  padding: 14px 30px;
  border: 2px solid #1a3a20;
  border-radius: 4px;
  background: #0a0a0a;
  color: #3eff7f;
  font-family: 'Courier New', ui-monospace, monospace;
  font-size: 16px; font-weight: 900;
  letter-spacing: 0.2em;
  cursor: pointer;
  text-shadow:
    0 0 6px rgba(62,255,127,0.8),
    0 0 14px rgba(62,255,127,0.4);
  box-shadow:
    inset 0 0 0 1px #0a1a10,
    inset 0 0 24px rgba(62,255,127,0.08),
    0 4px 14px rgba(0,0,0,0.5);
  overflow: hidden;
  transition: text-shadow 0.18s ease;
}
.btn-led-scanline {
  position: absolute; inset: 0;
  background: repeating-linear-gradient(
    0deg,
    rgba(62,255,127,0.05) 0 2px,
    transparent 2px 4px);
  pointer-events: none;
}
.btn-led-text { position: relative; }
.btn-led:hover {
  text-shadow:
    0 0 10px rgba(62,255,127,1),
    0 0 22px rgba(62,255,127,0.7),
    0 0 30px rgba(62,255,127,0.3);
}
<button class="btn-led">
  <span class="btn-led-scanline" aria-hidden="true"></span>
  <span class="btn-led-text">START</span>
</button>
29 / 43
Cassette Label
Pure CSS
Hot-pink mixtape J-card with electric-teal corner sticker and ruled lines. Hover gives it a 90s cassette tilt.
Try it
.btn-cass {
  position: relative;
  padding: 14px 32px 14px 38px;
  border: 2px solid #0a0a14;
  border-radius: 0;
  background:
    repeating-linear-gradient(0deg,
      transparent 0 13px,
      rgba(10,10,20,0.18) 13px 14px),
    #ff3d83;
  color: #0a0a14;
  font-family: ui-monospace, monospace;
  font-size: 14px; font-weight: 900;
  letter-spacing: 0.28em;
  cursor: pointer;
  box-shadow: 4px 4px 0 #0a0a14;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.btn-cass-corner {
  position: absolute;
  top: 4px; left: 4px;
  width: 16px; height: 16px;
  background: #00d4d4;
  border: 2px solid #0a0a14;
  transform: rotate(45deg);
  transform-origin: center;
}
.btn-cass-text { position: relative; }
.btn-cass:hover { transform: rotate(-1.5deg) translate(-1px,-1px); box-shadow: 5px 5px 0 #0a0a14; }
<button class="btn-cass">
  <span class="btn-cass-corner" aria-hidden="true"></span>
  <span class="btn-cass-text">SIDE A</span>
</button>
30 / 43
Chalkboard
Pure CSS
Slate-green chalkboard with a chalk-drawn label and dusty grain. Hover smudges a faint eraser trail.
Try it
.btn-chalk {
  position: relative;
  padding: 14px 30px;
  border: 4px solid #c9a15e;
  border-radius: 6px;
  background:
    repeating-linear-gradient(45deg,
      rgba(232,226,208,0.04) 0 1px,
      transparent 1px 4px),
    #1e2a23;
  color: #e8e2d0;
  font-family: 'Caveat', 'Comic Sans MS', cursive, ui-serif;
  font-size: 17px; font-weight: 700;
  letter-spacing: 0.04em;
  cursor: pointer;
  box-shadow: inset 0 0 24px rgba(0,0,0,0.5), 0 4px 12px rgba(0,0,0,0.3);
  overflow: hidden;
  transition: filter 0.18s ease;
}
.btn-chalk-dust {
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse at 30% 30%, rgba(232,226,208,0.06), transparent 60%),
    radial-gradient(ellipse at 70% 80%, rgba(232,226,208,0.04), transparent 60%);
  pointer-events: none; opacity: 0;
  transition: opacity 0.3s ease;
}
.btn-chalk-text { position: relative; text-shadow: 0 0 1px rgba(232,226,208,0.4); }
.btn-chalk:hover .btn-chalk-dust { opacity: 1; }
<button class="btn-chalk">
  <span class="btn-chalk-dust" aria-hidden="true"></span>
  <span class="btn-chalk-text">Today's Special</span>
</button>
31 / 43
Vinyl Sleeve
CSS + JS
Sleeve-red record cover with concentric grooves; click drops the needle and ripples outward from the click point.
Try it
.btn-vinyl {
  position: relative;
  width: 96px; height: 96px;
  border: 2px solid #1a1a1a;
  border-radius: 50%;
  background:
    radial-gradient(circle at center,
      #f5ead0 0%,
      #f5ead0 18%,
      #1a1a1a 19%,
      #1a1a1a 100%),
    #cc3232;
  background-size: 100% 100%;
  background-color: #cc3232;
  color: #cc3232;
  font-family: ui-serif, Georgia, serif;
  font-size: 12px; font-weight: 700;
  letter-spacing: 0.16em;
  cursor: pointer;
  display: grid; place-items: center;
  isolation: isolate;
  overflow: hidden;
  box-shadow:
    inset 0 0 0 1px rgba(255,255,255,0.05),
    0 4px 14px rgba(0,0,0,0.35);
  transition: transform 0.3s ease;
}
.btn-vinyl::before {
  content: '';
  position: absolute; inset: 12px;
  border-radius: 50%;
  background:
    repeating-radial-gradient(circle, #1a1a1a 0 1px, #2a2a2a 1px 3px);
  pointer-events: none;
}
.btn-vinyl::after {
  content: '';
  position: absolute; top: 50%; left: 50%;
  width: 0; height: 0;
  border-radius: 50%;
  background: rgba(245,234,208,0.4);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.btn-vinyl.btn-vinyl-rippled::after {
  animation: btn-vinyl-ripple 0.7s ease-out;
}
@keyframes btn-vinyl-ripple {
  0%   { width: 0; height: 0; opacity: 0.7; }
  100% { width: 200%; height: 200%; opacity: 0; }
}
.btn-vinyl:hover { transform: rotate(20deg); }
.btn-vinyl span,
.btn-vinyl  { isolation: isolate; }
/* label sits on top of grooves */
.btn-vinyl { z-index: 0; }
<button class="btn-vinyl">Play</button>
document.querySelectorAll('.btn-vinyl').forEach(function(btn) {
  btn.addEventListener('click', function() {
    btn.classList.remove('btn-vinyl-rippled');
    /* force reflow so the same animation can replay */
    void btn.offsetWidth;
    btn.classList.add('btn-vinyl-rippled');
  });
});
32 / 43
Polaroid Snapshot
Pure CSS
A genuine Polaroid frame — square sepia photo on top, the iconic chunky white bottom border with a handwritten caption and date stamp. Tilts casually on hover like it was just dropped on a table.
Try it
.btn-polar {
  position: relative;
  padding: 8px 8px 0;
  border: none;
  background: #fff8e7;
  cursor: pointer;
  display: flex; flex-direction: column;
  gap: 0;
  width: 156px;
  box-shadow:
    0 8px 22px rgba(0,0,0,0.35),
    0 1px 0 rgba(112,72,48,0.1) inset;
  transition: transform 0.3s cubic-bezier(.3,1,.3,1), box-shadow 0.3s ease;
  transform: rotate(-1.5deg);
}
.btn-polar-photo {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  background:
    radial-gradient(circle at 30% 25%, #ffd089 0%, transparent 45%),
    radial-gradient(circle at 70% 75%, rgba(0,0,0,0.25), transparent 50%),
    linear-gradient(135deg, #fcb938 0%, #b87333 50%, #5a3010 100%);
  filter: sepia(0.35) saturate(1.2) contrast(1.05);
  overflow: hidden;
}
.btn-polar-glare {
  position: absolute;
  top: -10%; left: -20%;
  width: 70%; height: 70%;
  background: radial-gradient(ellipse, rgba(255,255,255,0.4), transparent 60%);
  pointer-events: none;
}
.btn-polar-caption {
  /* The iconic chunky bottom border */
  display: flex; flex-direction: column;
  align-items: center;
  padding: 14px 10px 22px;
  gap: 4px;
}
.btn-polar-text {
  font-family: 'Caveat', 'Comic Sans MS', cursive, ui-serif;
  font-size: 16px; font-weight: 600;
  color: #3a2410;
  letter-spacing: 0.02em;
  line-height: 1;
}
.btn-polar-date {
  font-family: ui-monospace, monospace;
  font-size: 9px;
  letter-spacing: 0.16em;
  color: #b87333;
}
.btn-polar:hover {
  transform: rotate(0deg) translateY(-4px) scale(1.02);
  box-shadow: 0 14px 32px rgba(0,0,0,0.4);
}
<button class="btn-polar">
  <span class="btn-polar-photo" aria-hidden="true">
    <span class="btn-polar-glare" aria-hidden="true"></span>
  </span>
  <span class="btn-polar-caption">
    <span class="btn-polar-text">Save to album</span>
    <span class="btn-polar-date">'89 · 03/24</span>
  </span>
</button>
33 / 43
Boarding Pass
Pure CSS
Airline-navy ticket with three-letter airport codes, a flight-path arrow, magenta tear-line stub, and a real barcode strip along the bottom.
Try it
.btn-board {
  position: relative;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: #0c2340;
  color: #fdf6e9;
  font-family: ui-monospace, monospace;
  cursor: pointer;
  display: inline-flex; align-items: stretch;
  box-shadow: 0 6px 16px rgba(12,35,64,0.4);
  transition: transform 0.18s ease, box-shadow 0.18s ease;
  overflow: hidden;
}
.btn-board-main {
  display: flex; flex-direction: column;
  padding: 12px 18px 10px;
  gap: 8px;
}
.btn-board-route {
  display: flex; align-items: center; gap: 10px;
  font-size: 16px; font-weight: 800;
  letter-spacing: 0.12em;
}
.btn-board-arrow { width: 20px; height: 14px; color: #e84a8a; }
.btn-board-iata { color: #fdf6e9; }
.btn-board-barcode {
  display: block;
  height: 14px;
  background: repeating-linear-gradient(90deg,
    #fdf6e9 0 2px,
    transparent 2px 3px,
    #fdf6e9 3px 4px,
    transparent 4px 7px,
    #fdf6e9 7px 8px,
    transparent 8px 10px);
  opacity: 0.95;
}
.btn-board-tear {
  position: relative;
  width: 0;
  border-left: 2px dashed rgba(253,246,233,0.35);
  margin: 6px 0;
}
.btn-board-tear::before, .btn-board-tear::after {
  content: '';
  position: absolute;
  left: -7px; width: 12px; height: 12px;
  background: #17171f;
  border-radius: 50%;
}
.btn-board-tear::before { top: -12px; }
.btn-board-tear::after  { bottom: -12px; }
.btn-board-stub {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 12px 18px;
  background: linear-gradient(180deg, rgba(232,74,138,0.18), rgba(232,74,138,0.05));
  border-left: 1px solid rgba(232,74,138,0.4);
  gap: 2px;
}
.btn-board-stub-label {
  font-size: 9px; font-weight: 700;
  letter-spacing: 0.22em;
  color: #e84a8a;
}
.btn-board-stub-val {
  font-size: 18px; font-weight: 900;
  letter-spacing: 0.04em;
  color: #fdf6e9;
}
.btn-board:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 24px rgba(12,35,64,0.55);
}
<button class="btn-board">
  <span class="btn-board-main">
    <span class="btn-board-route">
      <span class="btn-board-iata">SFO</span>
      <svg
        class="btn-board-arrow"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        aria-hidden="true"
      >
        <path d="M2 12h18M14 5l7 7-7 7" />
      </svg>
      <span class="btn-board-iata">JFK</span>
    </span>
    <span class="btn-board-barcode" aria-hidden="true"></span>
  </span>
  <span class="btn-board-tear" aria-hidden="true"></span>
  <span class="btn-board-stub">
    <span class="btn-board-stub-label">SEAT</span>
    <span class="btn-board-stub-val">14A</span>
  </span>
</button>
34 / 43
Subway Token
CSS + JS
Aged brass subway token with a curved arc sweep that traces a clock-like ring around the inscription on click. The arc is an SVG path so the curve stays smooth at every angle.
Try it
.btn-token {
  position: relative;
  width: 124px; height: 124px;
  border: none; border-radius: 50%;
  background:
    radial-gradient(circle at 35% 30%, #e8c890 0%, #c9a15e 50%, #6a4520 100%);
  color: #2a1d0e;
  font-family: ui-serif, Georgia, serif;
  font-size: 11px; font-weight: 800;
  letter-spacing: 0.22em;
  cursor: pointer;
  display: grid; place-items: center;
  box-shadow:
    inset 0 -3px 6px rgba(0,0,0,0.4),
    inset 0 3px 4px rgba(255,232,180,0.6),
    0 6px 14px rgba(0,0,0,0.35);
  transition: transform 0.18s ease;
}
.btn-token-arc {
  position: absolute;
  inset: 8px;
  width: calc(100% - 16px); height: calc(100% - 16px);
  pointer-events: none;
}
.btn-token-arc-spin {
  transform-origin: 50% 50%;
  transform: rotate(-90deg);
  transition: transform 1.4s cubic-bezier(.3,1,.3,1);
}
.btn-token-text {
  position: relative; z-index: 1;
  text-shadow: 0 1px 0 rgba(255,232,180,0.4);
}
.btn-token.is-processing .btn-token-arc-spin {
  transform: rotate(270deg);
}
.btn-token:hover { transform: scale(1.04); }
<button class="btn-token">
  <svg class="btn-token-arc" viewBox="0 0 100 100" aria-hidden="true">
    <circle cx="50" cy="50" r="42" fill="none" stroke="rgba(42,29,14,0.18)" stroke-width="3" />
    <circle
      class="btn-token-arc-spin"
      cx="50"
      cy="50"
      r="42"
      fill="none"
      stroke="#2a1d0e"
      stroke-width="3"
      stroke-linecap="round"
      pathLength="100"
      stroke-dasharray="22 100"
    />
  </svg>
  <span class="btn-token-text">PROCESS</span>
</button>
document.querySelectorAll('.btn-token').forEach(function(btn) {
  btn.addEventListener('click', function() {
    if (btn.classList.contains('is-processing')) return;
    btn.classList.add('is-processing');
    setTimeout(function() { btn.classList.remove('is-processing'); }, 1400);
  });
});
35 / 43
Rx Label
Pure CSS
Cream pharmacy label with letterpress serif, dotted underline, and a red Rx badge. Vintage prescription chic.
Try it
.btn-rx {
  position: relative;
  padding: 12px 26px 12px 16px;
  border: 1.5px solid #4d5435;
  border-radius: 3px;
  background:
    repeating-linear-gradient(0deg,
      transparent 0 22px,
      rgba(77,84,53,0.08) 22px 23px),
    #f5ede0;
  color: #4d5435;
  font-family: ui-serif, Georgia, serif;
  font-size: 14px; font-weight: 700;
  letter-spacing: 0.18em;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 14px;
  box-shadow: 0 4px 10px rgba(77,84,53,0.18);
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.btn-rx-badge {
  width: 26px; height: 26px;
  background: #a32424;
  color: #f5ede0;
  border-radius: 3px;
  display: grid; place-items: center;
  font-family: ui-serif, Georgia, serif;
  font-size: 14px; font-weight: 800;
}
.btn-rx-text { border-bottom: 1.5px dotted #4d5435; padding-bottom: 1px; }
.btn-rx:hover { transform: translateY(-2px); box-shadow: 0 8px 18px rgba(77,84,53,0.22); }
<button class="btn-rx">
  <span class="btn-rx-badge" aria-hidden="true">℞</span>
  <span class="btn-rx-text">TAKE ONE</span>
</button>
36 / 43
Radio Dial
CSS + JS
Bakelite-cream dial with a brass pointer locked to OFF. Click toggles to ON: the pointer swings clockwise to the right tick mark and stays there until the next click.
Try it
.btn-radio {
  position: relative;
  width: 124px; height: 64px;
  border: 3px solid #1a1a1a;
  border-bottom-width: 6px;
  border-radius: 124px 124px 0 0 / 124px 124px 0 0;
  background: radial-gradient(ellipse at 50% 90%, #f5ede0 0%, #d8c8a8 100%);
  cursor: pointer;
  overflow: hidden;
  font-family: ui-serif, Georgia, serif;
  font-size: 9px; font-weight: 800;
  letter-spacing: 0.14em;
  box-shadow: 0 4px 14px rgba(0,0,0,0.25);
}
.btn-radio-tick {
  position: absolute; bottom: 6px;
  color: #5a4520;
  transition: color 0.3s ease, font-weight 0.3s ease;
}
.btn-radio-tick-l { left: 14px; color: #a32424; }
.btn-radio-tick-r { right: 14px; }
.btn-radio[aria-pressed="true"] .btn-radio-tick-l { color: #5a4520; }
.btn-radio[aria-pressed="true"] .btn-radio-tick-r { color: #a32424; }
.btn-radio-arrow {
  position: absolute;
  bottom: 0; left: 50%;
  width: 4px; height: 44px;
  background: linear-gradient(180deg, #c9a15e 0%, #6a4520 100%);
  border-radius: 2px 2px 0 0;
  transform-origin: bottom center;
  transform: translateX(-2px) rotate(-40deg);
  transition: transform 0.55s cubic-bezier(.3,1.2,.3,1);
}
.btn-radio[aria-pressed="true"] .btn-radio-arrow {
  transform: translateX(-2px) rotate(40deg);
}
<button class="btn-radio" aria-pressed="false" aria-label="Toggle power">
  <span class="btn-radio-tick btn-radio-tick-l" aria-hidden="true">OFF</span>
  <span class="btn-radio-tick btn-radio-tick-r" aria-hidden="true">ON</span>
  <span class="btn-radio-arrow" aria-hidden="true"></span>
</button>
document.querySelectorAll('.btn-radio').forEach(function(btn) {
  btn.addEventListener('click', function() {
    var pressed = btn.getAttribute('aria-pressed') === 'true';
    btn.setAttribute('aria-pressed', pressed ? 'false' : 'true');
  });
});
Advertisement
37 / 43
Postage Stamp
Pure CSS
Regal blue stamp with scalloped perforated edges and a faded ink date. Pure SVG-mask perforation.
Try it
.btn-stamp {
  position: relative;
  padding: 16px 22px;
  border: none;
  background: #1a3a78;
  color: #fdf6e9;
  font-family: ui-serif, Georgia, serif;
  font-size: 11px; font-weight: 800;
  letter-spacing: 0.22em;
  cursor: pointer;
  display: grid; gap: 4px;
  /* Scalloped perforated edges via radial mask */
  -webkit-mask:
    radial-gradient(circle 4px at 0 50%, transparent 99%, #000 100%) repeat-y left,
    radial-gradient(circle 4px at 100% 50%, transparent 99%, #000 100%) repeat-y right,
    radial-gradient(circle 4px at 50% 0, transparent 99%, #000 100%) repeat-x top,
    radial-gradient(circle 4px at 50% 100%, transparent 99%, #000 100%) repeat-x bottom,
    linear-gradient(#000, #000);
  -webkit-mask-size: 8px 12px, 8px 12px, 12px 8px, 12px 8px, 100% 100%;
  -webkit-mask-composite: source-over;
          mask:
    radial-gradient(circle 4px at 0 50%, transparent 99%, #000 100%) repeat-y left,
    radial-gradient(circle 4px at 100% 50%, transparent 99%, #000 100%) repeat-y right,
    radial-gradient(circle 4px at 50% 0, transparent 99%, #000 100%) repeat-x top,
    radial-gradient(circle 4px at 50% 100%, transparent 99%, #000 100%) repeat-x bottom,
    linear-gradient(#000, #000);
          mask-size: 8px 12px, 8px 12px, 12px 8px, 12px 8px, 100% 100%;
          mask-composite: add;
  box-shadow: 0 4px 12px rgba(26,58,120,0.4);
  transition: transform 0.2s ease;
}
.btn-stamp-num {
  font-size: 18px; color: #fcb938;
  letter-spacing: 0.08em;
}
.btn-stamp:hover { transform: rotate(-3deg) translateY(-2px); }
<button class="btn-stamp">
  <span class="btn-stamp-text">FIRST CLASS</span>
  <span class="btn-stamp-num">¢25</span>
</button>
38 / 43
Cigar Band
Pure CSS
Gold filigree foil seal over a maroon ribbon; ornate Victorian club-lounge energy.
Try it
.btn-cigar {
  position: relative;
  padding: 14px 36px;
  border: 2px solid #d4a017;
  border-radius: 2px;
  background:
    repeating-radial-gradient(circle at 50% 50%, rgba(212,160,23,0.1) 0 1px, transparent 1px 4px),
    repeating-linear-gradient(90deg, rgba(212,160,23,0.06) 0 2px, transparent 2px 6px),
    linear-gradient(180deg, #6a2540 0%, #5a1d2c 60%, #3a0e1a 100%);
  color: #d4a017;
  font-family: ui-serif, Georgia, serif;
  font-size: 13px; font-weight: 800;
  letter-spacing: 0.28em;
  cursor: pointer;
  box-shadow:
    inset 0 0 0 4px rgba(212,160,23,0.15),
    inset 0 1px 0 rgba(212,160,23,0.4),
    0 6px 16px rgba(58,14,26,0.4);
  text-shadow: 0 1px 0 rgba(0,0,0,0.4);
  transition: filter 0.18s, transform 0.2s;
}
.btn-cigar-edge {
  position: absolute; inset: 4px;
  border: 1px solid #d4a017;
  pointer-events: none;
}
.btn-cigar:hover { filter: brightness(1.1); transform: translateY(-1px); }
<button class="btn-cigar">
  <span class="btn-cigar-edge" aria-hidden="true"></span>
  <span class="btn-cigar-text">PRIVATE CLUB</span>
</button>
39 / 43
Calculator Key
Pure CSS
Beige concave keycap with olive-green accent label; click flips the press shadow flush.
Try it
.btn-calc {
  display: inline-block;
  padding: 0;
  border: none;
  background: transparent;
  cursor: pointer;
}
.btn-calc-cap {
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px;
  width: 84px; height: 64px;
  background: linear-gradient(180deg, #ebe4d3 0%, #d8d2c0 60%, #c0b9a3 100%);
  border-radius: 6px;
  box-shadow:
    inset 0 -6px 8px rgba(0,0,0,0.12),
    inset 0 2px 3px rgba(255,255,255,0.7),
    0 4px 0 #4d5435,
    0 6px 12px rgba(0,0,0,0.2);
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}
.btn-calc-glyph {
  font-family: ui-monospace, monospace;
  font-size: 22px; font-weight: 900;
  color: #4d5435;
}
.btn-calc-label {
  font-family: ui-monospace, monospace;
  font-size: 8px; font-weight: 700;
  letter-spacing: 0.18em;
  color: #4d5435;
  opacity: 0.8;
}
.btn-calc:active .btn-calc-cap {
  transform: translateY(3px);
  box-shadow:
    inset 0 -2px 4px rgba(0,0,0,0.18),
    inset 0 2px 3px rgba(255,255,255,0.5),
    0 1px 0 #4d5435,
    0 2px 4px rgba(0,0,0,0.15);
}
<button class="btn-calc">
  <span class="btn-calc-cap">
    <span class="btn-calc-glyph">=</span>
    <span class="btn-calc-label">EQUAL</span>
  </span>
</button>
40 / 43
Toolbox Latch
CSS + JS
Enamel teal lever with chrome bolt; the cursor spotlight illuminates the metal as you move across.
Try it
.btn-latch {
  position: relative;
  padding: 14px 32px 14px 50px;
  border: 2px solid #1a1a1a;
  border-radius: 6px;
  background:
    radial-gradient(circle 80px at var(--mx, 50%) var(--my, 50%),
      rgba(255,255,255,0.18), transparent 70%),
    linear-gradient(180deg, #0e7a5c 0%, #0d6048 60%, #084030 100%);
  color: #e0f0ec;
  font-family: ui-monospace, monospace;
  font-size: 13px; font-weight: 800;
  letter-spacing: 0.22em;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(8,64,48,0.4), inset 0 1px 0 rgba(255,255,255,0.15);
  --mx: 50%; --my: 50%;
}
.btn-latch-bolt {
  position: absolute;
  left: 12px; top: 50%;
  width: 18px; height: 18px;
  margin-top: -9px;
  background: radial-gradient(circle at 35% 30%, #f5f5f5, #8a8a85 60%, #3a3a32 100%);
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px #1a1a1a, 0 1px 2px rgba(0,0,0,0.4);
}
.btn-latch-bolt::before, .btn-latch-bolt::after {
  content: '';
  position: absolute; top: 50%; left: 50%;
  width: 12px; height: 2px;
  background: #1a1a1a;
  transform: translate(-50%, -50%);
}
.btn-latch-bolt::after { transform: translate(-50%, -50%) rotate(90deg); }
<button class="btn-latch">
  <span class="btn-latch-bolt" aria-hidden="true"></span>
  <span class="btn-latch-text">UNLOCK</span>
</button>
document.querySelectorAll('.btn-latch').forEach(function(btn) {
  btn.addEventListener('mousemove', function(e) {
    var rect = btn.getBoundingClientRect();
    btn.style.setProperty('--mx', (e.clientX - rect.left) + 'px');
    btn.style.setProperty('--my', (e.clientY - rect.top) + 'px');
  });
  btn.addEventListener('mouseleave', function() {
    btn.style.setProperty('--mx', '50%');
    btn.style.setProperty('--my', '50%');
  });
});
41 / 43
Receipt Paper
CSS + JS
Newsprint-cream receipt with a dashed perforation along the top, a tilted "PAID" rubber stamp watermark in the top-left corner, and a total that counts up on click before resetting.
Try it
.btn-receipt {
  position: relative;
  padding: 28px 26px 16px;
  border: none;
  background: #f4ecd8;
  color: #1a1a1a;
  font-family: ui-monospace, monospace;
  font-size: 12px; font-weight: 800;
  letter-spacing: 0.16em;
  cursor: pointer;
  filter: drop-shadow(0 4px 10px rgba(0,0,0,0.18));
  transition: transform 0.18s ease;
  overflow: hidden;
}
.btn-receipt-perf {
  position: absolute;
  top: 0; left: 0; right: 0; height: 6px;
  background-image:
    radial-gradient(circle 3px at 50% 0, transparent 99%, #f4ecd8 100%);
  background-size: 12px 6px;
  background-repeat: repeat-x;
  background-position: top;
  border-bottom: 1px dashed #8a7d5a;
  pointer-events: none;
}
.btn-receipt-stamp {
  position: absolute;
  top: 14px; left: 8px;
  font-family: ui-serif, Georgia, serif;
  font-size: 10px;
  color: #aa2a2a;
  border: 1.5px solid #aa2a2a;
  padding: 1px 5px;
  transform: rotate(-14deg);
  transform-origin: left center;
  letter-spacing: 0.14em;
  opacity: 0.55;
  pointer-events: none;
}
.btn-receipt-row {
  display: inline-flex; align-items: baseline; gap: 24px;
  padding-left: 38px;
}
.btn-receipt-text { color: #1a1a1a; }
.btn-receipt-amt {
  color: #aa2a2a; font-weight: 800;
  font-size: 18px;
  letter-spacing: 0.04em;
  min-width: 64px;
  display: inline-block;
}
.btn-receipt:hover { transform: translateY(-2px); }
<button class="btn-receipt">
  <span class="btn-receipt-perf" aria-hidden="true"></span>
  <span class="btn-receipt-stamp" aria-hidden="true">PAID</span>
  <span class="btn-receipt-row">
    <span class="btn-receipt-text">TOTAL</span>
    <span class="btn-receipt-amt" data-target="142">$0</span>
  </span>
</button>
document.querySelectorAll('.btn-receipt').forEach(function(btn) {
  btn.addEventListener('click', function() {
    var amt = btn.querySelector('.btn-receipt-amt');
    if (!amt || amt.dataset.animating === '1') return;
    amt.dataset.animating = '1';
    var target = parseInt(amt.dataset.target, 10) || 0;
    var start = 0;
    var startTime = null;
    var duration = 700;
    function step(ts) {
      if (!startTime) startTime = ts;
      var t = Math.min((ts - startTime) / duration, 1);
      var eased = 1 - Math.pow(1 - t, 3);
      var val = Math.round(start + (target - start) * eased);
      amt.textContent = '$' + val;
      if (t < 1) requestAnimationFrame(step);
      else { setTimeout(function() { amt.textContent = '$0'; amt.dataset.animating = ''; }, 1200); }
    }
    requestAnimationFrame(step);
  });
});
42 / 43
Punch Card
CSS + JS
Manila IBM-style punch card with the iconic clipped top-right corner, a stamped FORTRAN-style header at the top, and a real grid of column-and-row holes punched through the body. On hover the whole card follows the cursor magnetically.
Try it
.btn-punch {
  position: relative;
  padding: 28px 32px 18px;
  border: 1.5px solid #8a7d4a;
  background: #e3d4a8;
  color: #2a1d0e;
  font-family: ui-monospace, monospace;
  font-size: 14px; font-weight: 800;
  letter-spacing: 0.28em;
  cursor: pointer;
  /* Iconic clipped top-right corner */
  clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 14px, 100% 100%, 0 100%);
  box-shadow: 0 4px 12px rgba(58,36,16,0.3);
  transition: transform 0.25s cubic-bezier(.3,1,.5,1);
  text-align: center;
  display: block;
  --px: 0px; --py: 0px;
}
.btn-punch-header {
  position: absolute;
  top: 6px; left: 14px;
  font-family: ui-monospace, monospace;
  font-size: 8px; font-weight: 700;
  color: #5a4520;
  letter-spacing: 0.22em;
  border-bottom: 1px solid #8a7d4a;
  padding-bottom: 2px;
  width: calc(100% - 36px);
  text-align: left;
}
/* The hole grid: a single repeating background that draws columns of
   1.5×4 mm rectangular holes — the visual signature of an 80-column card.
   Two rows of perforations, evenly spaced, with ink-black holes. */
.btn-punch-grid {
  position: absolute;
  top: 22px; bottom: 4px;
  left: 8px; right: 8px;
  background-image:
    radial-gradient(2px 4px at 50% 50%, #1a1a1a 99%, transparent 100%),
    radial-gradient(2px 4px at 50% 50%, rgba(0,0,0,0.18) 99%, transparent 100%);
  background-size: 14px 22px, 14px 22px;
  background-position: 0 0, 0 11px;
  background-repeat: repeat-x, repeat-x;
  pointer-events: none;
  opacity: 0.85;
  -webkit-mask: linear-gradient(180deg,
    #000 0 30%, transparent 30% 60%, #000 60% 100%);
          mask: linear-gradient(180deg,
    #000 0 30%, transparent 30% 60%, #000 60% 100%);
}
.btn-punch-text {
  position: relative;
  z-index: 1;
  display: inline-block;
  background: #e3d4a8;
  padding: 2px 12px;
}
.btn-punch:hover { transform: translate(var(--px), var(--py)); }
<button class="btn-punch">
  <span class="btn-punch-header">RUN COMPILE 0030</span>
  <span class="btn-punch-grid" aria-hidden="true"></span>
  <span class="btn-punch-text">COMPILE</span>
</button>
document.querySelectorAll('.btn-punch').forEach(function(btn) {
  btn.addEventListener('mousemove', function(e) {
    var rect = btn.getBoundingClientRect();
    var x = (e.clientX - rect.left - rect.width  / 2) * 0.18;
    var y = (e.clientY - rect.top  - rect.height / 2) * 0.18;
    btn.style.setProperty('--px', x + 'px');
    btn.style.setProperty('--py', y + 'px');
  });
  btn.addEventListener('mouseleave', function() {
    btn.style.setProperty('--px', '0px');
    btn.style.setProperty('--py', '0px');
  });
});
43 / 43
Periscope Lens
Pure CSS
Brass periscope rim around a glowing cyan lens with crosshair etching. Hover pulses the lens like a sub coming online.
Try it
.btn-peri {
  position: relative;
  width: 110px; height: 110px;
  border: none; border-radius: 50%;
  background:
    radial-gradient(circle at center,
      #0a1a2a 0%,
      #0a3a5a 35%,
      #00d4ff 75%,
      #6ef0ff 100%);
  color: #0a1a2a;
  font-family: ui-monospace, monospace;
  font-size: 11px; font-weight: 800;
  letter-spacing: 0.22em;
  cursor: pointer;
  display: grid; place-items: center;
  isolation: isolate;
  box-shadow:
    inset 0 0 0 8px #b87333,
    inset 0 0 0 10px #6a3f15,
    0 0 24px rgba(0,212,255,0.3),
    0 6px 14px rgba(0,0,0,0.4);
}
.btn-peri-rim {
  position: absolute; inset: 12px;
  border: 1.5px solid rgba(0,212,255,0.5);
  border-radius: 50%;
  pointer-events: none;
  animation: btn-peri-pulse 2.4s ease-in-out infinite;
}
.btn-peri-cross {
  position: absolute; inset: 12px;
  pointer-events: none;
}
.btn-peri-cross::before, .btn-peri-cross::after {
  content: '';
  position: absolute;
  background: rgba(0,212,255,0.5);
}
.btn-peri-cross::before {
  top: 50%; left: 0; right: 0; height: 1px;
  transform: translateY(-50%);
}
.btn-peri-cross::after {
  left: 50%; top: 0; bottom: 0; width: 1px;
  transform: translateX(-50%);
}
.btn-peri-text {
  position: relative; z-index: 1;
  background: rgba(0,212,255,0.85);
  padding: 4px 8px;
  border-radius: 2px;
  color: #0a1a2a;
  text-shadow: 0 0 4px rgba(0,212,255,0.4);
}
@keyframes btn-peri-pulse {
  0%, 100% { transform: scale(1);    opacity: 0.7; }
  50%      { transform: scale(1.05); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-peri-rim { animation: none; }
}
.btn-peri:hover {
  box-shadow:
    inset 0 0 0 8px #b87333,
    inset 0 0 0 10px #6a3f15,
    0 0 36px rgba(0,212,255,0.55),
    0 6px 14px rgba(0,0,0,0.4);
}
<button class="btn-peri">
  <span class="btn-peri-rim" aria-hidden="true"></span>
  <span class="btn-peri-cross" aria-hidden="true"></span>
  <span class="btn-peri-text">SCAN</span>
</button>
Advertisement

Build your own

Tweak the exact look in our visual generators — no signup, instant copy-paste.

FAQ

Frequently asked questions

How do I style a button with CSS?
Start from a real button element, then style it: set padding, a background or gradient, border-radius, font-weight, and cursor: pointer. Add a :hover and :active state with a transition so the change is smooth, and a :focus-visible outline so keyboard users can see focus. Every button design in this gallery is built this way — open any demo's code panel to copy the exact CSS.
Should I use a button or an a element for a CSS button?
Use a button element when the control performs an action on the same page (submit, toggle, add to cart, open a dialog). Use an a element when it navigates to another URL. They can look identical with CSS, but the semantics matter for accessibility and SEO: a button is announced as a button and fires on Space, a link is announced as a link and is crawlable. Don't style a div as a button — it loses keyboard and screen-reader support.
Do these CSS buttons need JavaScript?
Most are pure CSS. The visual designs — gradients, materials, and hover and click effects like the magnetic mercury ripple, neon plasma burst, clay press and glitch — are done entirely with CSS transitions, animations and pseudo-elements. A few interactive use-case buttons (Download Progress, Confirm Delete, Subscribe Confetti) include a small self-contained JS snippet in the JS tab for behaviour CSS can't do.
How do I add a ripple or click effect to a button in CSS?
Use a pseudo-element that scales up from the centre. Put position: relative on the button, add an ::after that starts at scale(0) with a radial-gradient or solid circle, and on :active animate it to a larger scale with fading opacity. The Magnetic Mercury and ink-spread demos here show copy-paste ripple effects that need no JavaScript.
Are these CSS buttons accessible and free to use?
Yes to both. Every demo uses a real button element with a visible :focus-visible state, sufficient colour contrast, and aria-label on icon-only buttons; continuous animations honour prefers-reduced-motion. All 43 designs are MIT licensed — free for personal and commercial projects, no attribution required.

Related collections

Search CodeFronts

Loading…