BUTTONS

Interactive Shred Delete Button

A creative delete button with a letter shredding effect, particle animation, loading transition and animated success state.

HTML
<div class="cr-shred-demo">

  <button
    class="cr-shred-btn"
    id="crShredBtn"
    type="button"
    aria-label="Delete item"
  >

    <span class="cr-trash" aria-hidden="true">

      <span class="cr-trash-lid"></span>

      <span class="cr-trash-body">
        <span></span>
        <span></span>
      </span>

    </span>

    <span
      class="cr-delete-text"
      id="crDeleteText"
    >
      Delete
    </span>

    <span
      class="cr-loader-ring"
      aria-hidden="true"
    ></span>

    <span
      class="cr-success-check"
      aria-hidden="true"
    >
      ✓
    </span>

  </button>

</div>
CSS
*{
  box-sizing:border-box;
}

.cr-shred-demo{
  width:100%;
  min-height:360px;

  display:flex;
  align-items:center;
  justify-content:center;

  padding:40px 20px;

  background:
    radial-gradient(
      circle at 50% 45%,
      rgba(239,68,68,.12),
      transparent 35%
    ),
    #050914;

  font-family:Inter,Arial,sans-serif;
}

.cr-shred-btn{
  position:relative;

  min-width:190px;
  height:58px;

  display:flex;
  align-items:center;
  justify-content:center;
  gap:12px;

  padding:0 26px;

  overflow:visible;

  border:1px solid rgba(248,113,113,.35);
  border-radius:16px;

  background:
    linear-gradient(
      135deg,
      #17101a,
      #0d111c
    );

  color:#ffffff;

  font-family:inherit;
  font-size:15px;
  font-weight:800;

  cursor:pointer;

  box-shadow:
    0 18px 45px rgba(0,0,0,.38),
    0 0 25px rgba(239,68,68,.08);

  transition:
    width .45s cubic-bezier(.77,0,.18,1),
    min-width .45s cubic-bezier(.77,0,.18,1),
    border-radius .45s ease,
    background .3s ease,
    border-color .3s ease,
    transform .25s ease,
    box-shadow .3s ease;
}

.cr-shred-btn:hover{
  transform:translateY(-2px);

  border-color:rgba(248,113,113,.65);

  box-shadow:
    0 22px 55px rgba(0,0,0,.42),
    0 0 28px rgba(239,68,68,.16);
}


/* =========================================
   TRASH ICON
========================================= */

.cr-trash{
  position:relative;

  width:22px;
  height:25px;

  flex:0 0 22px;

  display:block;

  color:#fb7185;
}

.cr-trash-lid{
  position:absolute;

  left:2px;
  top:2px;

  width:18px;
  height:3px;

  border-radius:3px;

  background:currentColor;

  transform-origin:3px 50%;

  transition:
    transform .28s cubic-bezier(.34,1.56,.64,1);
}

.cr-trash-lid::before{
  content:"";

  position:absolute;

  left:6px;
  top:-4px;

  width:7px;
  height:3px;

  border:2px solid currentColor;
  border-bottom:0;

  border-radius:3px 3px 0 0;
}

.cr-trash-body{
  position:absolute;

  left:3px;
  top:8px;

  width:16px;
  height:16px;

  border:2px solid currentColor;
  border-top:0;

  border-radius:0 0 4px 4px;
}

.cr-trash-body span{
  position:absolute;

  top:3px;

  width:2px;
  height:8px;

  border-radius:2px;

  background:currentColor;

  opacity:.75;
}

.cr-trash-body span:first-child{
  left:4px;
}

.cr-trash-body span:last-child{
  right:4px;
}

.cr-shred-btn.cr-lid-open .cr-trash-lid{
  transform:
    translateY(-4px)
    rotate(-24deg);
}


/* =========================================
   DELETE LETTERS
========================================= */

.cr-delete-text{
  position:relative;

  display:flex;
  align-items:center;

  white-space:nowrap;
}

.cr-letter{
  position:relative;

  display:inline-block;

  will-change:
    transform,
    opacity,
    filter;
}

.cr-letter.cr-shredding{
  animation:
    crLetterShred
    .55s
    cubic-bezier(.55,.05,.35,1)
    forwards;
}

@keyframes crLetterShred{

  0%{
    transform:
      translate(0,0)
      rotate(0)
      scale(1);

    opacity:1;
    filter:blur(0);
  }

  45%{
    opacity:1;
  }

  100%{
    transform:
      translate(
        var(--cr-x),
        var(--cr-y)
      )
      rotate(var(--cr-rotate))
      scale(.12);

    opacity:0;
    filter:blur(2px);
  }

}


/* =========================================
   PARTICLES
========================================= */

.cr-particle{
  position:fixed;

  z-index:99999;

  width:3px;
  height:3px;

  border-radius:50%;

  background:#fb7185;

  pointer-events:none;

  box-shadow:
    0 0 8px rgba(251,113,133,.8);

  animation:
    crParticleMove
    .48s
    ease-in
    forwards;
}

@keyframes crParticleMove{

  0%{
    transform:
      translate(0,0)
      scale(1);

    opacity:1;
  }

  100%{
    transform:
      translate(
        var(--px),
        var(--py)
      )
      scale(.15);

    opacity:0;
  }

}


/* =========================================
   COLLAPSED STATE
========================================= */

.cr-shred-btn.cr-collapsed{
  min-width:58px;
  width:58px;

  padding:0;

  gap:0;

  border-radius:50%;

  border-color:rgba(99,102,241,.48);

  background:#0b1220;

  transform:none;
}

.cr-shred-btn.cr-collapsed .cr-trash,
.cr-shred-btn.cr-collapsed .cr-delete-text{
  opacity:0;
  visibility:hidden;
}


/* =========================================
   LOADER
========================================= */

.cr-loader-ring{
  position:absolute;

  left:50%;
  top:50%;

  width:28px;
  height:28px;

  margin:-14px 0 0 -14px;

  border:3px solid rgba(148,163,184,.18);
  border-top-color:#22d3ee;
  border-right-color:#6366f1;

  border-radius:50%;

  opacity:0;

  transform:scale(.6);

  pointer-events:none;
}

.cr-shred-btn.cr-loading .cr-loader-ring{
  opacity:1;

  transform:scale(1);

  animation:
    crLoaderSpin
    .7s
    linear
    infinite;

  box-shadow:
    0 0 16px rgba(34,211,238,.14);
}

@keyframes crLoaderSpin{

  to{
    transform:
      scale(1)
      rotate(360deg);
  }

}


/* =========================================
   SUCCESS
========================================= */

.cr-success-check{
  position:absolute;

  left:50%;
  top:50%;

  opacity:0;

  color:#34d399;

  font-size:25px;
  font-weight:900;
  line-height:1;

  transform:
    translate(-50%,-50%)
    scale(.3);

  pointer-events:none;
}

.cr-shred-btn.cr-success-state{
  border-color:rgba(52,211,153,.55);

  box-shadow:
    0 0 28px rgba(52,211,153,.15);
}

.cr-shred-btn.cr-success-state .cr-success-check{
  animation:
    crSuccessPop
    .45s
    cubic-bezier(.34,1.56,.64,1)
    forwards;
}

@keyframes crSuccessPop{

  0%{
    opacity:0;

    transform:
      translate(-50%,-50%)
      scale(.3);
  }

  100%{
    opacity:1;

    transform:
      translate(-50%,-50%)
      scale(1);
  }

}


/* =========================================
   COMPLETE / DELETED
========================================= */

.cr-shred-btn.cr-complete{
  box-shadow:
    0 0 30px rgba(52,211,153,.14);
}

.cr-shred-btn.cr-complete .cr-success-check{
  opacity:0;

  transition:
    opacity .25s ease;
}

.cr-shred-btn.cr-complete .cr-delete-text{
  opacity:1;
  visibility:visible;
}


/* =========================================
   MOBILE
========================================= */

@media(max-width:600px){

  .cr-shred-demo{
    min-height:300px;
    padding:30px 15px;
  }

  .cr-shred-btn{
    min-width:175px;
    height:56px;
  }

}
JavaScript
(() => {

  const btn =
    document.getElementById("crShredBtn");

  const text =
    document.getElementById("crDeleteText");

  const trash =
    btn.querySelector(".cr-trash");

  if (!btn || !text || !trash) {
    return;
  }

  let running = false;

  const wait = (ms) =>
    new Promise(
      resolve => setTimeout(resolve, ms)
    );


  function buildLetters(label = "Delete") {

    text.innerHTML = "";

    [...label].forEach(char => {

      const span =
        document.createElement("span");

      span.className = "cr-letter";

      span.textContent =
        char === " "
          ? "\u00A0"
          : char;

      text.appendChild(span);

    });

  }


  function prepareLetters() {

    const trashRect =
      trash.getBoundingClientRect();

    const targetX =
      trashRect.left +
      trashRect.width / 2;

    const targetY =
      trashRect.top +
      trashRect.height / 2;

    const letters =
      [...text.querySelectorAll(".cr-letter")];

    letters.forEach(
      (letter, index) => {

        const rect =
          letter.getBoundingClientRect();

        const startX =
          rect.left +
          rect.width / 2;

        const startY =
          rect.top +
          rect.height / 2;

        const x =
          targetX - startX;

        const y =
          targetY - startY;

        const rotation =
          index % 2 === 0
            ? -28 - index * 5
            : 26 + index * 5;

        letter.style.setProperty(
          "--cr-x",
          `${x}px`
        );

        letter.style.setProperty(
          "--cr-y",
          `${y}px`
        );

        letter.style.setProperty(
          "--cr-rotate",
          `${rotation}deg`
        );

      }
    );

    return letters;

  }


  function createParticles(letter) {

    const letterRect =
      letter.getBoundingClientRect();

    const trashRect =
      trash.getBoundingClientRect();

    const targetX =
      trashRect.left +
      trashRect.width / 2;

    const targetY =
      trashRect.top +
      trashRect.height / 2;

    const amount = 4;

    for (
      let i = 0;
      i < amount;
      i++
    ) {

      const particle =
        document.createElement("span");

      particle.className =
        "cr-particle";

      const startX =
        letterRect.left +
        Math.random() *
        letterRect.width;

      const startY =
        letterRect.top +
        Math.random() *
        letterRect.height;

      particle.style.left =
        `${startX}px`;

      particle.style.top =
        `${startY}px`;

      particle.style.setProperty(
        "--px",
        `${targetX - startX}px`
      );

      particle.style.setProperty(
        "--py",
        `${targetY - startY}px`
      );

      particle.style.animationDelay =
        `${Math.random() * 70}ms`;

      document.body.appendChild(
        particle
      );

      particle.addEventListener(
        "animationend",
        () => particle.remove(),
        { once:true }
      );

    }

  }


  async function runDelete() {

    if (running) {
      return;
    }

    running = true;

    btn.classList.add(
      "cr-working",
      "cr-lid-open"
    );

    const letters =
      prepareLetters();

    await wait(180);

    for (const letter of letters) {

      createParticles(letter);

      letter.classList.add(
        "cr-shredding"
      );

      await wait(95);

    }

    await wait(500);

    btn.classList.remove(
      "cr-lid-open"
    );

    btn.classList.add(
      "cr-collapsed"
    );

    await wait(520);

    btn.classList.add(
      "cr-loading"
    );

    await wait(1100);

    btn.classList.remove(
      "cr-loading"
    );

    btn.classList.add(
      "cr-success-state"
    );

    await wait(850);

    btn.classList.add(
      "cr-complete"
    );

    await wait(550);

    resetAsDeleted();

  }


  function resetAsDeleted() {

    btn.classList.remove(
      "cr-collapsed",
      "cr-success-state"
    );

    trash.style.display = "none";

    text.innerHTML = "";

    const deleted =
      document.createElement("span");

    deleted.className =
      "cr-letter";

    deleted.textContent =
      "Deleted";

    text.appendChild(deleted);

    running = false;

    btn.addEventListener(
      "click",
      restoreButton,
      { once:true }
    );

  }


  function restoreButton(event) {

    event.stopPropagation();

    btn.classList.remove(
      "cr-working",
      "cr-complete"
    );

    trash.style.display = "";

    buildLetters("Delete");

    setTimeout(
      () => {

        btn.addEventListener(
          "click",
          runDelete,
          { once:true }
        );

      },
      50
    );

  }


  buildLetters("Delete");

  btn.addEventListener(
    "click",
    runDelete,
    { once:true }
  );

})();

How This Interactive Shred Delete Button Works

The Interactive Shred Delete Button turns a standard delete action into a staged visual sequence. Its HTML uses one main button containing a custom trash icon, the “Delete” label, a loader ring, and a success checkmark. The button also includes aria-label="Delete item", while decorative elements such as the trash icon, loader, and checkmark are hidden from assistive technologies with aria-hidden="true".

The trash icon is drawn entirely with CSS rather than an image or SVG. The .cr-trash-lid and .cr-trash-body elements form the bin, while two small child spans create the vertical lines inside it. When JavaScript adds the cr-lid-open class, the lid moves upward by 4px and rotates -24deg. A cubic-bezier transition gives this movement a quick opening effect.

JavaScript first converts the word “Delete” into individual .cr-letter spans. Before shredding begins, prepareLetters() uses getBoundingClientRect() to calculate the center positions of the trash icon and every letter. It then stores the required horizontal movement, vertical movement, and rotation in the CSS custom properties --cr-x, --cr-y, and --cr-rotate.

Each letter receives the cr-shredding class in sequence, with a 95ms delay between letters. The crLetterShred animation runs for 0.55s, moving each character toward the trash while rotating it, shrinking it to 0.12 scale, fading its opacity to zero, and adding a 2px blur. This combination creates the shredding effect without needing a canvas or animation library. For more information about this animation technique, see the MDN CSS animations documentation.

JavaScript also generates four particles for each letter. Their starting positions are randomized inside the letter’s bounding rectangle, while CSS variables direct them toward the center of the trash icon. The particles use fixed positioning and a 0.48s animation, then remove themselves from the DOM when animationend fires.

After the letters disappear, the button changes through several CSS states. The cr-collapsed class reduces it to a circular 58px button. Next, cr-loading displays a 28px loader that rotates continuously every 0.7s. The script waits 1100ms, removes the loader, and adds cr-success-state, which displays a green checkmark with a 0.45s scale animation.

Finally, the button expands again and displays “Deleted”. Clicking it in this state calls restoreButton(), restores the trash icon, rebuilds the individual “Delete” letters, and prepares the button to run the sequence again. A running flag also prevents the deletion animation from being started multiple times while it is already active.

Customizing the Interactive Shred Delete Button

The appearance and timing can be adjusted through the existing CSS values and JavaScript delays without changing the basic structure.

  • Change the 190px minimum width and 58px height to resize the desktop button.
  • Adjust the 16px border radius to make the initial button more or less rounded.
  • Modify #17101a and #0d111c to change the button’s gradient background.
  • Change #fb7185 to give the trash icon and shredding particles another color.
  • Adjust the .55s duration on crLetterShred to control how quickly individual letters disappear.
  • Change the 95ms JavaScript wait between letters to increase or reduce the staggered shredding sequence.
  • Modify amount = 4 in createParticles() to control how many particles are generated for each character.
  • Adjust the 1100ms loading wait or .7s loader rotation duration to change the processing stage.
  • Edit the 600px media query, 175px mobile minimum width, or 56px mobile height for different responsive behavior.

Where to Use This Interactive Shred Delete Button

This component works best when a destructive action benefits from clear visual feedback showing that the action has moved through processing and completion states.

  • Delete controls for dashboard records or list items.
  • File and document removal interfaces.
  • Task-management applications where users can remove completed or unwanted tasks.
  • Admin panels with destructive item actions.
  • Demo interfaces that need an animated delete-state transition.
  • Interactive prototypes where deletion needs visible processing and success feedback.

Because the component runs through several timed states, the actual data-removal logic can be integrated around the appropriate JavaScript stage when adapting it to a real application. For another button interaction based on pointer movement rather than deletion states, the Magnetic Hover Button is a related CodeRipple component.

Frequently Asked Questions

Does this delete button use JavaScript?

Yes. JavaScript handles the complete interaction sequence, including splitting the text into letters, calculating their movement, generating particles, changing button state classes, displaying the loader and success state, and restoring the button after another click.

How does each letter move toward the trash icon?

The script measures both the letter and trash icon with getBoundingClientRect(). It calculates the difference between their center coordinates and passes those values to CSS through --cr-x and --cr-y. The keyframe animation then uses those variables inside translate().

Why does the button become circular during the animation?

The script adds the cr-collapsed class after shredding finishes. That class sets both the width and minimum width to 58px, removes the horizontal padding and gap, applies a 50% border radius, and hides the trash and delete text. This leaves a compact area for the loader and success check.

Is the Interactive Shred Delete Button responsive?

Yes. A media query activates at widths up to 600px. It reduces the demo’s minimum height from 360px to 300px, changes its padding, and reduces the button to a 175px minimum width and 56px height.

The Interactive Shred Delete Button combines CSS state classes, keyframe animations, DOM measurements, generated particles, and timed JavaScript transitions to provide clear visual feedback from the initial delete action through the final “Deleted” state.

Scroll to Top