Case study · Open source

react-smart-copy

An open-source, headless React library for clipboard interactions. It models the full interaction around a copy (copying, copied, failed, retry, and paste) so product teams get honest feedback, accessible triggers, and typed errors without writing the same fragile clipboard code again.

npm install react-smart-copy

Project proof

My role

  • Designed the API, state model, and accessibility contract, and wrote the library in strict TypeScript.
  • Wrote the documentation, the support matrix of what browsers can and cannot copy, and the live demo.
  • Published and maintain the package on npm under the MIT license.

Constraints

  • The library could promise only what browsers actually support, and had to report everything else as a typed error.
  • It had to stay unstyled and work with Tailwind, CSS modules, CSS-in-JS, and component libraries.
  • It had to be safe for server rendering, React StrictMode, and both React 18 and 19.

Key decisions

  • Split a framework-agnostic core from the React layer so the same state machine runs anywhere.
  • Separated the real status from the display status to prevent flicker and layout shift.
  • Built keyboard access, stable names, and live-region announcements into the components by default.
Project type
Open-source React library on npm
Role
Author and maintainer
Primary focus
Accessibility, typed errors, DX
Stack area
TypeScript, React 18+, Clipboard API
  • Open source
  • React
  • TypeScript
  • Clipboard API
  • Accessibility
  • npm package

Context

Copy buttons appear in almost every product: API keys, IDs, email addresses, invite links, and code samples. They are usually written as a single clipboard call with a "Copied" message, and they are rewritten in every codebase.

That single call hides most of the real behavior. Browsers refuse clipboard writes outside HTTPS, block them when focus moves, support only some formats, and report failures inconsistently. The interface around the call has to explain all of that to the person who pressed the button.

Problem

Typical copy implementations fail in ways users cannot see and teams rarely test. The goal was a library that treats the copy interaction as product behavior with defined states, not as a utility function.

  • Failures such as an insecure context or a denied permission often no-op silently.
  • A copy takes a few milliseconds, so naive status text flashes "Copying…" for one frame and resizes the button.
  • Copy buttons hidden with display: none on hover are unreachable by keyboard.
  • Results are rarely announced to screen-reader users.
  • Server rendering can mismatch when components read the browser clipboard during render.

Approach

The library is built in layers. A framework-agnostic core holds a copy state machine, payload validation, a browser clipboard adapter, and error classification. The React layer adds hooks and a compound CopyField component on top of that core.

Every state is explicit and observable: idle, copying, copied, and error. Two statuses separate logic from display: the real status changes instantly for tests and logic, while the display status only shows "Copying…" when a copy is slow and then holds it long enough to avoid a blink.

  • Copies text, rich HTML with a required plain-text fallback, PNG images, JSON, and several formats at once.
  • Pastes text, HTML, and screenshots through usePaste, with keyboard paste that needs no permission prompt.
  • CopyGroup coordinates rows so only one shows "Copied" at a time.
  • A capture entry point copies an SVG or a piece of UI as a PNG.
  • Custom adapters let hosts such as Electron provide their own clipboard.

Accessibility decisions

Accessibility rules are part of the components rather than documentation for consumers to remember. The trigger is always a real button, and its accessible name stays stable while the result is announced separately.

  • Triggers are reachable with Tab and activate with Enter or Space.
  • Revealed actions stay in the DOM and the tab order; they are hidden with opacity, never display: none.
  • Keyboard focus pins the reveal; a mouse click does not.
  • The trigger is never disabled while copying, so keyboard focus is not lost.
  • The live region is mounted before its first message, so the first announcement is not missed.
  • Animations respect prefers-reduced-motion.

Errors, SSR, and footprint

Every failure is a typed, classified error such as unsupported, insecure-context, permission-denied, not-focused, unsupported-format, or too-large, and each one states whether a retry can help. Nothing fails silently.

The React entry ships with "use client" and never touches window during render, so server and first client render match in Next.js. The package has zero runtime dependencies, and the README reports about 3.2 kB for useCopy and 5.4 kB for all copy features, minified and compressed with Brotli.

Outcomes

  • Published on npm as react-smart-copy under the MIT license, with documentation and a live demo on GitHub Pages.
  • Supports copy for text, rich HTML, PNG, JSON, and multi-format payloads, plus paste for text, HTML, and images.
  • Replaces silent clipboard failures with typed errors that state whether a retry can help.
  • Runs on this portfolio through its framework-agnostic core, powering the copy buttons without loading React.

Next steps

  • Read the documentation and try the live demo.
  • Install it with npm install react-smart-copy.
  • Report issues or contribute on GitHub.