← Back to gallery
MIXED

Copy to Clipboard Pulse

A copy button that waits for Clipboard.writeText() to resolve before playing a success pulse, with a separate error branch for NotAllowedError and insecure contexts.

Duration
2s
Resolution
1440×900
Format
MIXED
clipboardcopy-feedbackconfirmationpromise-resolveerror-branch

Feedback / Clipboard API / promise-resolve

Copy to clipboard pulse

Reimplements a copy-feedback pulse that fires only after Clipboard.writeText() resolves, with a distinct error branch when the Promise rejects (secure context or permission denial).

api token — success hold 1.20s
sk_live_51NabxyYOURTOKENgoes_here

Secret-sensitive copy

API Token

A compact copy button reveals a short success pulse only after Clipboard.writeText() resolves, and branches to an error message on NotAllowedError.

  • api token
  • secret
  • promise
pnpm dlx create-next-app@latest animation-gallery --typescript

Install / run block

Shell Command

A code block with an inline copy button above it. Success shows a subtle check; failure surfaces a tooltip pointing the user to manual selection.

  • shell
  • install
  • tooltip
https://animationpatterns.art/animations/copy-to-clipboard-pulse

Share menu primary action

Share Link

A share-link row with a wider success reveal timing so users on busy pages notice the confirmation before the pulse fades.

  • share
  • url
  • longer hold

Copy inspector

API Token

sk_live_51NabxyYOURTOKENgoes_here

Click Copy to fire the success pulse.

Success hold
1.20s
On reject
error branch

Never assume success optimistically — the pulse and the "Copied" label both wait for the Promise to resolve before rendering.

tsx
const handleCopy = async () => {
  try {
    await navigator.clipboard.writeText(text);
    // only set success after the Promise resolves
    setStatus('success');
    setTimeout(() => setStatus('idle'), 1200);
  } catch (error) {
    // secure-context or NotAllowedError branch
    setStatus('error');
    setTimeout(() => setStatus('idle'), 1200);
  }
};

.copy-button[data-status="success"] {
  background: #7dd3fc;
  color: #0f172a;
  animation: copyPulse 1.20s ease-out;
}

.copy-button[data-status="error"] {
  background: rgba(248, 113, 113, 0.2);
  color: #fca5a5;
}

@keyframes copyPulse {
  0%   { transform: scale(1); }
  25%  { transform: scale(1.08); }
  100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .copy-button[data-status="success"] { animation: none; }
}