Back to Nicholas

emil kowalski ui building a hold to delete component

Nicholas
@nicholas

--- captured: 2026-05-23T12:03:16-07:00 tags: - ai-research - ui - design - curl-md --- --- description: A clever use of clip path. url:

Uploaded
Uploaded May 27, 2026
File type
MD
Queried
0

Full document

Showing the full document.

--- source: https://emilkowal.ski/ui/building-a-hold-to-delete-component author: Emil Kowalski captured: 2026-05-23T12:03:16-07:00 tags: - ai-research - ui - design - curl-md --- --- title: Building a Hold to Delete Component description: A clever use of clip path. url: https://emilkowal.ski/ui/building-a-hold-to-delete-component site: Emil Kowalski --- # Building a Hold to Delete Component I recently shared [this component](https://x.com/emilkowalski/status/1901689149154869746) on X, and a lot of people liked it. This article briefly explains how it’s built. Hold to Delete 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. App.jsstyles.css ``` "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> ``` In this case, `.button` has `position: relative` and `.hold-overlay` is absolutely positioned on top of it. This creates the following effect: Hold to Delete Hold to Delete ## 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); } ``` 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](/ui/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); } ``` This will make the overlay visible instantly once the button is pressed: Hold to Delete Hold to Delete 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 to Delete Hold to Delete 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; } ``` 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](/ui/great-animations#great-animations-are-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); } ``` This gives us a nice hold to delete button. Here’s the final code: App.jsstyles.css ``` "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.dev](https://animations.dev/) Some of the components we build in the course. [NextBuilding a Drawer Component](/ui/building-a-drawer-component) --- Powered by [curl.md](https://curl.md)

Want to learn more?

Ask about this document