/Click feedback
Click feedback • React
Fuse Undo Button
An archive button that turns into Undo and lights an orange fuse around its outline; the fuse holds still while you hover it, so the chance to undo only runs out once you look away.
Installation
Requires lucide-react for its icons. The shadcn CLI installs it for you; copying the code by hand means installing it yourself.
Terminal
npx shadcn@latest add @microkit/fuse-undo-buttonCode
1"use client";23import { Archive, RotateCcw } from "lucide-react";4import { useState } from "react";5import type { AnimationEvent } from "react";67type FuseState = "idle" | "burning";89const fuseKeyframes = `10@keyframes fuse-undo-burn {11from { stroke-dashoffset: 0; }12to { stroke-dashoffset: -1.02; }13}`;1415const rowClass =16"inline-flex items-center gap-[10px] justify-self-center whitespace-nowrap [grid-area:1/1] [transition:opacity_.3s_ease,transform_.45s_cubic-bezier(.16,1,.3,1),visibility_.3s] data-[active=false]:invisible data-[active=false]:opacity-0 motion-reduce:transition-none motion-reduce:data-[active=false]:[transform:none]";1718export function FuseUndoButton() {19const [state, setState] = useState<FuseState>("idle");20const [fuse, setFuse] = useState(0);2122const handleBurnEnd = (event: AnimationEvent<SVGRectElement>) => {23if (event.animationName === "fuse-undo-burn") setState("idle");24};2526const handleClick = () => {27if (state === "idle") {28setFuse((current) => current + 1);29setState("burning");30} else {31setState("idle");32}33};3435return (36<>37<style>{fuseKeyframes}</style>38<button39type="button"40className="group relative inline-flex h-[52px] cursor-pointer appearance-none items-center justify-center rounded-full border-0 bg-[light-dark(#ffffff,#121418)] px-[28px] [font-family:inherit] text-[17px] font-medium leading-none tracking-[-.01em] text-[light-dark(#14181e,#f2f4f6)] [box-shadow:inset_0_0_0_1px_light-dark(#dfe3ea,#2b2f36),0_10px_24px_light-dark(rgba(246,247,249,.35),rgba(0,0,0,.35))] [transition:background-color_.2s_ease] hover:bg-[light-dark(#f0f2f5,#1b1e23)] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#f97316] motion-reduce:transition-none"41data-state={state}42onClick={handleClick}43>44<svg45key={fuse}46className="pointer-events-none absolute inset-0 size-full overflow-visible opacity-0 [filter:drop-shadow(0_0_6px_rgba(249,115,22,.55))] [transition:opacity_.35s_ease] group-data-[state=burning]:opacity-100 motion-reduce:transition-none"47aria-hidden="true"48>49<rect50className="fill-none stroke-[#f97316] stroke-2 [stroke-linecap:round] [animation-name:fuse-undo-burn] [animation-duration:5s] [animation-timing-function:linear] [animation-fill-mode:forwards] [animation-play-state:paused] group-data-[state=burning]:[animation-play-state:running] group-data-[state=burning]:group-hover:[animation-play-state:paused] group-data-[state=burning]:group-focus-visible:[animation-play-state:paused]"51x="0"52y="0"53width="100%"54height="100%"55rx="26"56pathLength={1}57strokeDasharray="1 2"58onAnimationEnd={handleBurnEnd}59/>60</svg>61<span className="grid" aria-live="polite">62<span className={`${rowClass} data-[active=false]:[transform:translateY(-10px)]`} data-active={state === "idle"}>63<Archive className="shrink-0" size={20} strokeWidth={2} aria-hidden="true" />64Archive65</span>66<span className={`${rowClass} data-[active=false]:[transform:translateY(10px)]`} data-active={state === "burning"}>67<RotateCcw className="shrink-0" size={20} strokeWidth={2} aria-hidden="true" />68Undo69</span>70</span>71</button>72</>73);74}