↩ Experiments

Confirm Button

November 2023

Hold to confirm

Confirmed.

The Value

The typical flow for a user taking a permanent action is to have the user click a button, then have a confirmation dialog modal pop up, and finally have the user click another button to confirm the action. While this does prevent most accidental actions, it also can be streamlined to reduce friction. This component allows the user to confirm or cancel an action with a single click, while still preventing accidental actions.

Logic

The timer logic for the button was done using a ref, state and interval. The refwas used to store the interval and not have it be re-created on every render frame. The state was used to store the percentage of the bar that was filled. The interval was used to increment the percentage every 30 milliseconds.

holdingRef.current = setInterval(() => {
    isHoldingRef.current = true; 
    if(counter <= (100 - speed)) {
        counter = counter + speed;
        setConfirmationPercentage(counter);
    }
}, 30);

I found that any faster than 30ms for the state update caused dropped frames on most devices when updating the bar's width.

Animation

The keen eyed among you may notice that the text on the button isn't statically colored, but rather dynamically changes based on the width of the bar. This was done using the mix-blend-mode CSS property and setting it to difference. This property allows the text to be colored based on the background color of the element behind it. Basically, black background = white text, white background = black text.

For the label's text, I used an animation combining opacity and the translateY property to show the confirmation message. This gives the impression that the text is rotating into or out of place.

To further the response to the user's input, I animated the scale of the button to give the user a visual cue that the button was being pressed, and a spring animation for the confirmation to indicate that the action was complete.

The Big Bug

Every now and then, you encounter a bug that just does not make sense. You triple-check all of your logic, you check the documentation, you check StackOverflow and ChatGPT, and you still can't figure out why it's happening. This was one of those bugs. Here is a video of the bug in question:

This isn't a video of me letting my mouse go and the bar percentage resetting. The bar percentage is resetting while my mouse is being held, and seemingly at random.

At first, I figured that there was just probably some incorrect math in the way I was handling the progress bar percentage/width. That wasn't it. Then I figured, maybe the setInterval function being called every 30ms is causing some frame weirdness. That wasn't it either. I even got to questioning my entire understanding of how the DOM works. Still no luck.

Eventually, by sheer luck (or willpower depending on how you look at it), while confirming this button over a hundred times, I realized that the bar is actually only resetting when it gets to my mouse. Then it clicked, the onMouseEnter event from the progress bar was being triggered when my mouse was over the bar, which in turn fired the onMouseUp event on the button, which reset the bar percentage.

This was fixed by adding e.stopPropagation() to the onMouseEnter event of the progress bar, which prevented the child event from bubbling up to the parent button.