About this snippet

A track with a draggable thumb, used to make a dangerous action deliberate. Drag it to the end and the action commits. Let go early and it springs back.

Movement runs on pointer events with setPointerCapture, so the drag keeps tracking even when the cursor leaves the button or the page. Travel is clamped between zero and the track width less the thumb and its padding, which stops the thumb overshooting its own container at speed.

Release checks the position against 90 percent of the available travel. Anything short of that snaps back with a spring easing, and a success sets a completed flag that blocks further dragging. The timeout after success stands in for your own request, so replace it with the real call and clear that flag if the request fails.

Size
5.5 KB
Uses
HTML, CSS, JavaScript
Loads
Nothing external
Views
366
<div class="action-card">
  <div class="alert-icon">
    <svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="#ef4444" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
  </div>
  
  <h2>Delete Repository</h2>
  <p>This action cannot be undone. All files, commits, and branches will be erased permanently.</p>
  
  <div class="swipe-container" id="swipe-container">
    <div class="swipe-bg"></div>
    <div class="swipe-fill" id="swipe-fill"></div>
    <div class="swipe-text" id="swipe-text">Slide to Delete</div>
    
    <button class="swipe-thumb" id="swipe-thumb" aria-label="Drag to confirm">
      <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
    </button>
  </div>
</div>
*, *::before, *::after {
  box-sizing: border-box;
}

body {
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  margin: 0;
  background: #020617;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  padding: 20px;
}

.action-card {
  background: #0f172a;
  padding: 40px;
  border-radius: 24px;
  border: 1px solid #1e293b;
  box-shadow: 0 25px 50px -12px rgba(0,0,0,0.6);
  width: 100%;
  max-width: 420px;
  text-align: center;
}

.alert-icon {
  width: 64px;
  height: 64px;
  background: rgba(239, 68, 68, 0.1);
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 0 auto 20px auto;
  box-shadow: 0 0 20px rgba(239, 68, 68, 0.2);
}

.action-card h2 {
  color: #f8fafc;
  margin: 0 0 10px 0;
  font-size: 1.5rem;
}

.action-card p {
  color: #94a3b8;
  line-height: 1.6;
  margin: 0 0 40px 0;
  font-size: 0.95rem;
}

.swipe-container {
  position: relative;
  width: 100%;
  height: 64px;
  border-radius: 32px;
  background: #1e293b;
  border: 1px solid #334155;
  overflow: hidden;
  display: flex;
  align-items: center;
  /* Crucial for mobile dragging without scrolling */
  touch-action: none;
}

.swipe-bg {
  position: absolute;
  top: 0; left: 0; width: 100%; height: 100%;
  background: #1e293b;
  z-index: 1;
}

.swipe-fill {
  position: absolute;
  top: 0; left: 0; width: 64px; height: 100%;
  background: #ef4444;
  z-index: 2;
  border-radius: 32px 0 0 32px;
}

.swipe-text {
  position: absolute;
  top: 0; left: 0; width: 100%; height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  font-weight: 600;
  color: #94a3b8;
  z-index: 3;
  pointer-events: none;
  transition: opacity 0.3s, color 0.3s;
}

.swipe-thumb {
  position: absolute;
  top: 4px; left: 4px;
  width: 54px; height: 54px;
  background: #ffffff;
  border-radius: 50%;
  border: none;
  box-shadow: 0 4px 10px rgba(0,0,0,0.3);
  display: flex;
  justify-content: center;
  align-items: center;
  color: #ef4444;
  z-index: 4;
  cursor: grab;
}

.swipe-thumb:active {
  cursor: grabbing;
}

/* Secure Success State Styles */
.swipe-container.is-success {
  border-color: #ef4444;
  box-shadow: 0 0 20px rgba(239, 68, 68, 0.4);
}

.swipe-container.is-success .swipe-fill {
  width: 100% !important;
  border-radius: 32px;
  transition: width 0.3s ease;
}

.swipe-container.is-success .swipe-text {
  color: #ffffff;
  z-index: 5;
}

.swipe-container.is-success .swipe-thumb {
  opacity: 0;
}
const container = document.getElementById("swipe-container");
const thumb = document.getElementById("swipe-thumb");
const fill = document.getElementById("swipe-fill");
const text = document.getElementById("swipe-text");

let isDragging = false;
let startX = 0;
let currentX = 0;
let completed = false;

thumb.addEventListener("pointerdown", function(e) {
  if (completed) return;
  isDragging = true;
  startX = e.clientX;
  
  thumb.style.transition = "none";
  fill.style.transition = "none";
  text.style.opacity = "0.2";
  
  /* Securely capture the pointer to track movement outside the element */
  thumb.setPointerCapture(e.pointerId);
});

container.addEventListener("pointermove", function(e) {
  if (!isDragging || completed) return;
  
  /* Calculate limits accurately */
  const maxTravel = container.offsetWidth - thumb.offsetWidth - 8;
  
  currentX = e.clientX - startX;
  
  if (currentX < 0) currentX = 0;
  if (currentX > maxTravel) currentX = maxTravel;
  
  thumb.style.transform = "translateX(" + currentX + "px)";
  fill.style.width = (currentX + 64) + "px";
});

function handleDragEnd() {
  if (!isDragging || completed) return;
  isDragging = false;
  
  const maxTravel = container.offsetWidth - thumb.offsetWidth - 8;
  
  /* Strict 90% threshold for success */
  if (currentX > maxTravel * 0.9) {
    completed = true;
    
    container.classList.add("is-success");
    text.style.opacity = "1";
    text.innerText = "Deleting...";
    
    /* Simulate secure server request */
    setTimeout(function() {
      text.innerText = "Project Deleted";
    }, 1500);
    
  } else {
    /* Snap back securely */
    thumb.style.transition = "transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)";
    fill.style.transition = "width 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)";
    
    thumb.style.transform = "translateX(0px)";
    fill.style.width = "64px";
    text.style.opacity = "1";
    currentX = 0;
  }
}

thumb.addEventListener("pointerup", handleDragEnd);
thumb.addEventListener("pointercancel", handleDragEnd);
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save