Emil KowalskiDesign Engineer

Building a Hold to Delete Component

I recently shared this component on X, and a lot of people liked it. This article briefly explains how it's built.

Press and hold the button to see the transition.

The starting point

Our starting point is a simple, styled button. You can use the code editor below to follow along.

Code Playground
"use client";

export default function ClipPathButton() {
  return (
    <button className="button">
      <svg height="16" strokeLinejoin="round" viewBox="0 0 16 16" width="16">
        <path
          fillRule="evenodd"
          clipRule="evenodd"
          d="M6.75 2.75C6.75 2.05964 7.30964 1.5 8 1.5C8.69036 1.5 9.25 2.05964 9.25 2.75V3H6.75V2.75ZM5.25 3V2.75C5.25 1.23122 6.48122 0 8 0C9.51878 0 10.75 1.23122 10.75 2.75V3H12.9201H14.25H15V4.5H14.25H13.8846L13.1776 13.6917C13.0774 14.9942 11.9913 16 10.6849 16H5.31508C4.00874 16 2.92263 14.9942 2.82244 13.6917L2.11538 4.5H1.75H1V3H1.75H3.07988H5.25ZM4.31802 13.5767L3.61982 4.5H12.3802L11.682 13.5767C11.6419 14.0977 11.2075 14.5 10.6849 14.5H5.31508C4.79254 14.5 4.3581 14.0977 4.31802 13.5767Z"
          fill="currentColor"
        />
      </svg>
      Hold to Delete
    </button>
  );
}

Setting up the structure

Before we take care of the reveal transition, we need two versions of the button: the default monochrome one, and the version we want to gradually reveal.

<button className="button">
  <div aria-hidden="true" className="hold-overlay">
    <TrashIcon />
    Hold to Delete
  </div>
  <TrashIcon />
  Hold to Delete
</button>
<button className="button">
  <div aria-hidden="true" className="hold-overlay">
    <TrashIcon />
    Hold to Delete
  </div>
  <TrashIcon />
  Hold to Delete
</button>

In this case, .button has position: relative and .hold-overlay is absolutely positioned on top of it. This creates the following effect:

Revealing the overlay

Before revealing the overlay, we need to hide it first. We’ll use the inset shape of the clip-path property and set the right value to 100%. You can think of each inset value as a side of a rectangle, similar to the margin or padding shorthand.

.hold-overlay {
  clip-path: inset(0px 100% 0px 0px);
}
.hold-overlay {
  clip-path: inset(0px 100% 0px 0px);
}

This hides the red overlay from the right. Why from the right? Because by subtracting from our 100%, we can reveal the button from the left, which will create the effect we want. I cover clip-path in more detail in The Magic of Clip Path.

To reveal it, we'll use the :active pseudo-class. While the user holds the button, we'll set clip-path to inset(0px 0px 0px 0px).

.button:active .hold-overlay {
  clip-path: inset(0px 0px 0px 0px);
}
.button:active .hold-overlay {
  clip-path: inset(0px 0px 0px 0px);
}

This will make the overlay visible instantly once the button is pressed:

To add an animation, we'll use the transition property. Since we want a longer effect, we’ll use a duration of 2s. We should use linear timing function to reveal the overlay evenly.

.hold-overlay {
  transition: clip-path 2s linear;
}
.hold-overlay {
  transition: clip-path 2s linear;
}

That's it! Let's now add some polish to our button to make it feel better.

Polishing it up

The first thing that feels off is the transition when you release the button. Pressing should be slow to allow the user to confirm their choice, but the release can be much snappier.

To do that, I'll set the .hold-overlay transition to 200ms with an ease-out timing function. Then, in the :active state I'll override it with a 2s transition using a linear timing function.

.hold-overlay {
  transition: clip-path 200ms ease-out;
}
 
.button:active .hold-overlay {
  transition: clip-path 2s linear;
}
.hold-overlay {
  transition: clip-path 200ms ease-out;
}
 
.button:active .hold-overlay {
  transition: clip-path 2s linear;
}

This makes the release transition much faster while keeping the press transition slow enough for the user to confirm their choice. Remember: you should make your animations fast (most of the time).

One thing that makes any button feel better is a slight scale-down animation on press. It feels good because it gives the user instant feedback, making the UI feel more responsive, as if it's really listening to the user.

To do that, I'll add transform: scale(0.97) to the button's :active state and apply a snappy transition.

.button {
  transition: transform 160ms ease-out;
}
 
.button:active {
  transform: scale(0.97);
}
.button {
  transition: transform 160ms ease-out;
}
 
.button:active {
  transform: scale(0.97);
}

This gives us a nice hold to delete button. Here's the final code:

Code Playground
"use client";

export default function ClipPathButton() {
  return (
    <button className="button">
      <div aria-hidden="true" className="hold-overlay">
        <svg height="16" strokeLinejoin="round" viewBox="0 0 16 16" width="16">
          <path
            fillRule="evenodd"
            clipRule="evenodd"
            d="M6.75 2.75C6.75 2.05964 7.30964 1.5 8 1.5C8.69036 1.5 9.25 2.05964 9.25 2.75V3H6.75V2.75ZM5.25 3V2.75C5.25 1.23122 6.48122 0 8 0C9.51878 0 10.75 1.23122 10.75 2.75V3H12.9201H14.25H15V4.5H14.25H13.8846L13.1776 13.6917C13.0774 14.9942 11.9913 16 10.6849 16H5.31508C4.00874 16 2.92263 14.9942 2.82244 13.6917L2.11538 4.5H1.75H1V3H1.75H3.07988H5.25ZM4.31802 13.5767L3.61982 4.5H12.3802L11.682 13.5767C11.6419 14.0977 11.2075 14.5 10.6849 14.5H5.31508C4.79254 14.5 4.3581 14.0977 4.31802 13.5767Z"
            fill="currentColor"
          />
        </svg>
        Hold to Delete
      </div>
      <svg height="16" strokeLinejoin="round" viewBox="0 0 16 16" width="16">
        <path
          fillRule="evenodd"
          clipRule="evenodd"
          d="M6.75 2.75C6.75 2.05964 7.30964 1.5 8 1.5C8.69036 1.5 9.25 2.05964 9.25 2.75V3H6.75V2.75ZM5.25 3V2.75C5.25 1.23122 6.48122 0 8 0C9.51878 0 10.75 1.23122 10.75 2.75V3H12.9201H14.25H15V4.5H14.25H13.8846L13.1776 13.6917C13.0774 14.9942 11.9913 16 10.6849 16H5.31508C4.00874 16 2.92263 14.9942 2.82244 13.6917L2.11538 4.5H1.75H1V3H1.75H3.07988H5.25ZM4.31802 13.5767L3.61982 4.5H12.3802L11.682 13.5767C11.6419 14.0977 11.2075 14.5 10.6849 14.5H5.31508C4.79254 14.5 4.3581 14.0977 4.31802 13.5767Z"
          fill="currentColor"
        />
      </svg>
      Hold to Delete
    </button>
  );
}

More components like this

This component is coming from my animations course in which we build a lot of other things ranging from simple CSS animations to complex Framer Motion (now Motion) components.

Check out "Animations on the Web"

Some of the components we build in the course.