basic-setup
Install and render your first Turnstile widget with required configuration. Activate this skill when starting a new project with react-turnstile or when implementing basic CAPTCHA protection for the first time.
Basic Setup
Install and render a Cloudflare Turnstile widget in your React application.
Installation
# npm
npm install @marsidev/react-turnstile
# pnpm
pnpm add @marsidev/react-turnstile
# yarn
yarn add @marsidev/react-turnstile
# bun
bun add @marsidev/react-turnstile
Get Your Site Key
Before using Turnstile, you need a site key from Cloudflare:
- Go to the Cloudflare Turnstile dashboard
- Create a new widget
- Copy your site key (starts with 0x or 1x for testing)
Note: Site keys are NOT secret. They are safe to include in client-side code.
Basic Usage
import { Turnstile } from '@marsidev/react-turnstile'
export default function LoginForm() {
return (
<form>
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
{/* Basic Turnstile widget */}
<Turnstile siteKey="YOUR_SITE_KEY" />
<button type="submit">Login</button>
</form>
)
}
Widget Sizes
Choose the size that fits your layout:
| Size | Dimensions | Best For |
|---|---|---|
| normal (default) | 300×65px | Most forms, standard layouts |
| compact | 150×140px | Mobile, tight spaces |
| flexible | 100% width × 65px | Responsive layouts |
| invisible | 0×0px | Hidden widgets (requires Invisible widget type from Cloudflare) |
// Compact size for mobile
<Turnstile
siteKey="YOUR_SITE_KEY"
options={{ size: 'compact' }}
/>
// Flexible width
<Turnstile
siteKey="YOUR_SITE_KEY"
options={{ size: 'flexible' }}
/>
Handling the Success Token
Use the onSuccess callback to receive the verification token:
import { Turnstile } from '@marsidev/react-turnstile'
import { useState } from 'react'
export default function LoginForm() {
const [token, setToken] = useState<string | null>(null)
return (
<form>
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
<Turnstile
siteKey="YOUR_SITE_KEY"
onSuccess={(token) => setToken(token)}
/>
<button type="submit" disabled={!token}>
Login
</button>
</form>
)
}
Complete Form Example
'use client' // For Next.js App Router
import { Turnstile } from '@marsidev/react-turnstile'
import { useState } from 'react'
export default function ContactForm() {
const [token, setToken] = useState<string | null>(null)
const [isSubmitting, setIsSubmitting] = useState(false)
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
if (!token) return
setIsSubmitting(true)
// Send token to your server for validation
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, /* other form data */ })
})
if (response.ok) {
// Handle success
}
setIsSubmitting(false)
}
return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
<Turnstile
siteKey="YOUR_SITE_KEY"
onSuccess={setToken}
/>
<button type="submit" disabled={!token || isSubmitting}>
{isSubmitting ? 'Sending...' : 'Send Message'}
</button>
</form>
)
}
Common Mistakes
❌ Using Invisible Size Without Invisible Widget Type
The invisible size is only for the Invisible widget type from Cloudflare. Using it with a normal widget shows nothing.
Wrong:
// Shows nothing if widget type is not "Invisible"
<Turnstile siteKey="xxx" options={{ size: 'invisible' }} />
Correct:
// Use visible sizes for normal widgets
<Turnstile siteKey="xxx" options={{ size: 'normal' }} />
<Turnstile siteKey="xxx" options={{ size: 'compact' }} />
<Turnstile siteKey="xxx" options={{ size: 'flexible' }} />
Next Steps
- Customize appearance: See widget-customization skill
- Handle tokens properly: See token-lifecycle skill
- Next.js integration: See nextjs-ssr skill
- Multiple widgets: See multiple-widgets skill
Testing During Development
Use Cloudflare's test keys that always pass validation:
- Site key: 1x00000000000000000000AA
- Secret key: 1x0000000000000000000000000000000AA
See: https://developers.cloudflare.com/turnstile/troubleshooting/testing/