Components / Motion / Burst Button

Burst Button beta

A real call-to-action that answers a click with a burst of sparks — feedback you can see from across the room.

Preview

It fires once when it arrives. After that, it answers clicks only.

<div style="display:flex;gap:26px;flex-wrap:wrap;align-items:center;padding:16px 6px">
  <span class="k-burst" data-burst><canvas aria-hidden="true"></canvas><button class="k-burst-btn" type="button">Deploy to production</button></span>
  <span class="k-burst" data-burst><canvas aria-hidden="true"></canvas><button class="k-burst-btn teal" type="button">Publish the part</button></span>
</div>
<p style="font-size:12.5px;color:var(--k-ink-3)">It fires once when it arrives. After that, it answers clicks only.</p>
/* KATA Burst Button — a CTA with visible punctuation. */
.k-burst{position:relative;display:inline-block;font-family:var(--k-font)}
.k-burst canvas{position:absolute;inset:-70px -90px;width:calc(100% + 180px);height:calc(100% + 140px);pointer-events:none}
.k-burst-btn{position:relative;z-index:1;background:var(--k-ink);color:var(--k-bg);border:0;font-family:inherit;font-size:14.5px;font-weight:650;padding:13px 26px;border-radius:11px;cursor:pointer;transition:transform .12s}
.k-burst-btn:active{transform:scale(.96)}
.k-burst-btn.teal{background:var(--k-accent);color:var(--k-on-color)}
// KATA Burst Button — sparks from the point of the click.
document.querySelectorAll('[data-burst]').forEach(function (wrap) {
  var canvas = wrap.querySelector('canvas');
  var btn = wrap.querySelector('.k-burst-btn');
  var ctx = canvas.getContext('2d');
  var DPR = Math.min(3, window.devicePixelRatio || 1);
  var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  var cs = getComputedStyle(wrap);
  var COLORS = ['--k-accent', '--k-ink', '--k-zinc-400'].map(function (v) { return cs.getPropertyValue(v).trim(); }).filter(Boolean);
  var parts = [], raf = null, W = 0, H = 0;
  function size() { W = wrap.offsetWidth + 180; H = wrap.offsetHeight + 140; canvas.width = W * DPR; canvas.height = H * DPR; ctx.setTransform(DPR, 0, 0, DPR, 0, 0); }
  function fire(cx, cy) {
    if (reduced) return;
    size();
    var r = wrap.getBoundingClientRect();
    var ox = cx != null ? cx - r.left + 90 : W / 2;
    var oy = cy != null ? cy - r.top + 70 : H / 2;
    for (var i = 0; i < 30; i++) {
      var a = Math.PI * 2 * Math.random();
      var sp = 1.4 + Math.random() * 4.4;
      parts.push({ x: ox, y: oy, vx: Math.cos(a) * sp, vy: Math.sin(a) * sp - .8, life: 1, c: COLORS[i % COLORS.length], s: 2 + Math.random() * 2.6 });
    }
    if (!raf) tick();
  }
  function tick() {
    ctx.clearRect(0, 0, W, H);
    parts = parts.filter(function (p) { return p.life > 0; });
    parts.forEach(function (p) {
      p.vy += .11; p.vx *= .985; p.vy *= .985;
      p.x += p.vx; p.y += p.vy; p.life -= .02;
      ctx.globalAlpha = Math.max(0, p.life);
      ctx.fillStyle = p.c;
      ctx.beginPath(); ctx.arc(p.x, p.y, p.s, 0, 7); ctx.fill();
    });
    ctx.globalAlpha = 1;
    raf = parts.length ? requestAnimationFrame(tick) : null;
  }
  btn.addEventListener('click', function (e) { fire(e.clientX, e.clientY); });
  if ('IntersectionObserver' in window && !reduced) {
    var io = new IntersectionObserver(function (es) { if (es[0].isIntersecting) { setTimeout(function () { fire(); }, 450); io.disconnect(); } }, { threshold: .6 });
    io.observe(wrap);
  }
  size();
});

Installation

CLI

node kata-ui.mjs add burst-button

Writes the part’s CSS and JS into your project. New to the CLI? See the CLI doc.

Manual

  1. Load tokens.css once, before any part.
  2. Copy the CSS tab into your stylesheet.
  3. Copy the HTML where it belongs. The part has a script — include it after the markup; it initializes itself.

Usage

The button is an ordinary primary button; the burst is its click feedback, fired from the exact point of the click, in the token palette. Use it on the one action a page exists for — deploy, purchase, publish. A burst on every button is a fireworks stall; a burst on the button is punctuation.

Accessibility

The click still does its job with the script removed, and the burst is aria-hidden canvas. Under prefers-reduced-motion there is no burst — the button relies on its pressed state like its ancestors, which is sufficient because it is, first, a button.

Anatomy

The public surface of this part, read from its own source. Class names are the API — the CSS tab above is the full contract.

.k-burstclass
.k-burst-btnclass
.tealclass
data-burstdata hook
aria-hiddenARIA

Related parts