react-turnstile
A minimal React component and hook for rendering Cloudflare Turnstile CAPTCHA challenges.
Repository Health
Technical Analysis
react-turnstile is a lightweight wrapper around Cloudflare’s Turnstile CAPTCHA-alternative widget, giving React developers a declarative <Turnstile /> component instead of hand-rolling script loading and DOM management. It lazy-loads Cloudflare’s script once per page, uses explicit rendering to avoid duplicate widgets, and exposes every Turnstile callback (onVerify, onSuccess, onLoad, onError, onExpire, onTimeout, and more) as typed React props.
Beyond the component, the library ships a useTurnstile() hook and a “bound turnstile object” passed to every callback, so consumers can call execute, reset, getResponse, and isExpired against a specific widget instance without manually tracking the underlying widgetId. A fixedSize prop pre-allocates layout space to avoid cumulative layout shift while the widget loads asynchronously.
What You Get
- Declarative
<Turnstile />component with typed props for every Turnstile configuration option (sitekey, theme, language, size, appearance, execution mode) - Full callback coverage — onVerify, onSuccess, onLoad, onError, onExpire, onTimeout, onAfterInteractive, onBeforeInteractive, onUnsupported — each receiving a bound turnstile object
useTurnstile()hook for accessing the global Turnstile object once the script has finished loadingfixedSizeprop that reserves the widget’s final width/height up front to prevent layout shift during async load
Common Use Cases
- Gating a login or signup form behind a Turnstile challenge before submitting credentials to the backend
- Protecting a public contact or comment form from spam bots without a traditional CAPTCHA’s UX friction
- Invisible or interaction-only verification on checkout flows where visible challenges would hurt conversion
- Manually resetting or re-executing a challenge after a failed server-side token verification
Under The Hood
Architecture
The entire implementation lives in a single module (src/index.tsx, ~280 lines) built around a module-level singleton state machine (unloaded/loading/ready) that lazy-loads Cloudflare’s external script exactly once per page via a shared promise. The <Turnstile> component wires that promise into a useEffect that imperatively calls window.turnstile.render and tears the widget down on unmount or dependency change, while a mutable “in-place state” object (seeded via useState’s initial value but written to directly) sidesteps stale-closure issues inside the render effect’s captured callbacks. A second, separate useEffect keeps that mutable object’s callback references fresh across re-renders without re-triggering the widget-render effect, avoiding needless widget remounts on every prop change. createBoundTurnstileObject() wraps a widgetId into a small object exposing execute/reset/getResponse/isExpired so callers never track the id themselves. The library has no abstraction layer between itself and Cloudflare’s window.turnstile API — if that external surface changes shape, the wrapper breaks directly.
Tech Stack
Written entirely in TypeScript and compiled with a plain tsc build (no bundler such as esbuild or Rollup). Runtime dependencies are limited to peer dependencies on react and react-dom (>=16.13.1); the only devDependency of note is turnstile-types, supplying the upstream TS types (RenderParameters, SupportedLanguages, TurnstileObject) that the component’s props are built on. Output is published as a dual ESM/CJS package via the exports map, pointing at a single dist/index.js plus dist/index.d.ts. The Cloudflare Turnstile script itself is loaded at runtime from challenges.cloudflare.com, not bundled into the package.
Code Quality
No test files, test runner, or CI configuration are present in the repository — the library is untested beyond manual and community use. Error handling is minimal: a failed script load rejects a shared promise with a plain string (“Failed to load Turnstile.”) rather than a typed Error, and that rejection is funneled into the onError callback. TypeScript typing is thorough for the public API surface (dedicated TurnstileProps, TurnstileCallbacks, BoundTurnstileObject interfaces sourced from turnstile-types), though the module-level global namespace reference is typed as any. No ESLint configuration exists; only Prettier is wired up for formatting. Naming is consistent and idiomatic throughout.
API Design
The public surface is intentionally narrow — one component plus one hook — with every Turnstile option exposed as an individually typed, flat prop rather than a single opaque options object. The “bound turnstile object” pattern scopes execute/reset/getResponse/isExpired to a specific widget instance, removing the common footgun of a multi-widget page accidentally operating on the wrong challenge. The fixedSize prop collapses a well-known real pain point — layout shift from the widget’s asynchronously loaded iframe — into a single boolean. Getting started requires nothing more than <Turnstile sitekey="..." onVerify={...} />. The overall approach is not novel — several comparable Turnstile wrappers exist across ecosystems, including a competing package already listed in this catalog — but the ergonomics are clean for a narrow-purpose glue library.
Used by 3 apps in this directory
Cal.diy
Scheduling
The 100% MIT-licensed, community-driven scheduling platform — self-host your own booking infrastructure with no enterprise strings attached.
Formbricks
Forms Surveys · Marketing · Analytics
Open-source experience management platform for in-app, website, email, and link surveys — privacy-first and fully self-hostable.
Notesnook
Note Taking · File Storage · Security
End-to-end encrypted, open-source note-taking where your data stays yours — even from the server.