Skip to content

Swipe to Confirm

A copy-paste buttons component in pure HTML, CSS & vanilla JS. Zero dependencies, framework-agnostic, MIT-licensed.

ButtonsHTMLJavaScriptCSSany framework
Swipe to confirm

Copy into your project

HTML
<div class="nuda-swipe">
  <span class="nuda-swipe__bg">Swipe to confirm</span>
  <span class="nuda-swipe__fill" style="width:0"></span>
  <span class="nuda-swipe__thumb" style="left:0">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
         stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
      <path d="M9 18l6-6-6-6" />
    </svg>
  </span>
</div>
JavaScript
const root  = document.querySelector('.nuda-swipe');
const fill  = root.querySelector('.nuda-swipe__fill');
const thumb = root.querySelector('.nuda-swipe__thumb');

let dragging = false;
let startX = 0;
let pct = 0;

const setPct = (v) => {
  pct = Math.max(0, Math.min(100, v));
  const px = (root.clientWidth - 48) * (pct / 100);
  fill.style.width = pct + '%';
  thumb.style.left = px + 'px';
  if (pct >= 95) {
    root.dispatchEvent(new CustomEvent('confirm'));
    pct = 100;
  }
};

thumb.addEventListener('pointerdown', (e) => {
  dragging = true;
  startX = e.clientX - thumb.offsetLeft;
});
window.addEventListener('pointermove', (e) => {
  if (!dragging) return;
  const px = e.clientX - startX;
  setPct((px / (root.clientWidth - 48)) * 100);
});
window.addEventListener('pointerup', () => {
  dragging = false;
  if (pct < 95) setPct(0);
});
CSS
.nuda-swipe {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 240px;
  height: 46px;
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.08);
  border-radius: 99px;
  overflow: hidden;
  user-select: none;
}

.nuda-swipe__bg {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #a0a0a8;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .04em;
  z-index: 1;
  pointer-events: none;
}

.nuda-swipe__fill {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  background: linear-gradient(90deg,#e4ff54,#6ee7b7);
  border-radius: 99px;
  transition: width .35s cubic-bezier(.16,1,.3,1);
}

.nuda-swipe__thumb {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: #09090b;
  border: 2px solid #e4ff54;
  color: #e4ff54;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  box-shadow: 0 4px 14px -4px rgba(228,255,84,.5);
  transition: left .35s cubic-bezier(.16,1,.3,1);
}

.nuda-swipe__thumb svg {
  width: 18px;
  height: 18px;
  animation: _swipeNudge 1.4s ease-in-out infinite;
}

@keyframes _swipeNudge {
  0%,100% {
    transform: translateX(0);
  }
  50% {
    transform: translateX(3px);
  }
}

@media (prefers-reduced-motion:reduce) {
  .nuda-swipe__thumb svg {
    animation: none;
  }
}

How to use Swipe to Confirm

Paste the HTML where you need it and the CSS into a global stylesheet (or a <style> tag). Every class is prefixed nuda- so it never collides with Tailwind or your own styles. Tweak the CSS custom properties to match your design system.

Works in React, Vue, Svelte, Astro, Next.js, Nuxt, Laravel Blade, Django, Rails — or a single .html file. No npm install, no build step.

More buttons components

← Browse all NudaUI components