con un clic
widget-customization
// Customize widget appearance, theme, execution mode, and behavior. Activate when styling the widget, changing when/how tokens are generated, or integrating with specific UX flows like multi-step forms.
// Customize widget appearance, theme, execution mode, and behavior. Activate when styling the widget, changing when/how tokens are generated, or integrating with specific UX flows like multi-step forms.
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.
Handle multiple Turnstile widgets on the same page without conflicts. Activate when adding more than one CAPTCHA to a page, or when widgets interfere with each other.
Integrate Turnstile with Next.js, handle SSR, hydration, and App Router. Activate when building Next.js applications with App Router or Pages Router, or when encountering SSR-related errors.
Handle token generation, expiration, validation workflow, and form integration. Activate when implementing form submission with CAPTCHA, handling token expiration, or integrating with server-side validation.
| name | widget-customization |
| description | Customize widget appearance, theme, execution mode, and behavior. Activate when styling the widget, changing when/how tokens are generated, or integrating with specific UX flows like multi-step forms. |
| triggers | ["turnstile theme dark mode","turnstile invisible","turnstile execution mode","turnstile appearance","customize turnstile widget","turnstile callback not working","rerenderOnCallbackChange"] |
| category | core |
| metadata | {"library":"@marsidev/react-turnstile","library_version":"1.4.2","framework":"React"} |
Customize the Turnstile widget's appearance, behavior, and integration with your application's UX.
Control when the widget is visible to users:
always (default)Widget is always visible.
<Turnstile
siteKey="YOUR_SITE_KEY"
options={{ appearance: 'always' }}
/>
executeWidget is hidden until you call execute() method. Useful for custom triggers.
import { Turnstile } from '@marsidev/react-turnstile'
import type { TurnstileInstance } from '@marsidev/react-turnstile'
import { useRef } from 'react'
export default function CustomTrigger() {
const ref = useRef<TurnstileInstance>(null)
return (
<>
<Turnstile
ref={ref}
siteKey="YOUR_SITE_KEY"
options={{
appearance: 'execute',
execution: 'execute'
}}
/>
<button onClick={() => ref.current?.execute()}>
Verify I'm Human
</button>
</>
)
}
interaction-onlyWidget only appears when user interaction is required (most privacy-friendly).
<Turnstile
siteKey="YOUR_SITE_KEY"
options={{ appearance: 'interaction-only' }}
/>
Control when the token is generated:
render (default)Token is generated automatically when the widget renders.
executeToken is generated only when you explicitly call execute(). Useful for delaying verification.
Match the widget to your application's color scheme:
// Auto-detects from system preference
<Turnstile siteKey="xxx" options={{ theme: 'auto' }} />
// Force light theme
<Turnstile siteKey="xxx" options={{ theme: 'light' }} />
// Force dark theme
<Turnstile siteKey="xxx" options={{ theme: 'dark' }} />
Set the widget language:
// Auto-detect from browser
<Turnstile siteKey="xxx" options={{ language: 'auto' }} />
// Specific language (ISO 639-1)
<Turnstile siteKey="xxx" options={{ language: 'en' }} />
<Turnstile siteKey="xxx" options={{ language: 'es' }} />
<Turnstile siteKey="xxx" options={{ language: 'de' }} />
See all supported languages: https://developers.cloudflare.com/turnstile/reference/supported-languages/
By default, callbacks access the latest state without re-rendering the widget. For cases where you need the widget to re-render when callbacks change:
import { Turnstile } from '@marsidev/react-turnstile'
import { useCallback, useState } from 'react'
export default function DynamicForm() {
const [userType, setUserType] = useState<'user' | 'admin'>('user')
// IMPORTANT: Wrap callbacks with useCallback when using rerenderOnCallbackChange
const handleSuccess = useCallback((token: string) => {
if (userType === 'admin') {
// Handle admin login
} else {
// Handle user login
}
}, [userType])
return (
<Turnstile
siteKey="YOUR_SITE_KEY"
rerenderOnCallbackChange={true}
onSuccess={handleSuccess}
/>
)
}
⚠️ Warning: Without useCallback, this causes infinite re-renders!
For better control over script loading (recommended for Next.js):
import {
Turnstile,
SCRIPT_URL,
DEFAULT_SCRIPT_ID
} from '@marsidev/react-turnstile'
import Script from 'next/script'
export default function Page() {
return (
<>
<Script
id={DEFAULT_SCRIPT_ID}
src={SCRIPT_URL}
strategy="beforeInteractive"
/>
<Turnstile
siteKey="YOUR_SITE_KEY"
injectScript={false}
/>
</>
)
}
Change the HTML element used for the widget container:
// Default: div
<Turnstile siteKey="xxx" />
// Use span
<Turnstile siteKey="xxx" as="span" />
// Use custom component
<Turnstile siteKey="xxx" as="section" className="my-turnstile" />
Wrong:
<Turnstile
rerenderOnCallbackChange={true}
onSuccess={(token) => handleToken(token)} // New function every render!
/>
Correct:
const handleSuccess = useCallback((token: string) => {
handleToken(token)
}, [])
<Turnstile
rerenderOnCallbackChange={true}
onSuccess={handleSuccess}
/>
Wrong:
const ref = useRef<TurnstileInstance>(null)
// Button calls execute immediately
<button onClick={() => ref.current?.execute()}>Verify</button>
// Widget not configured for execution mode
<Turnstile ref={ref} siteKey="xxx" />
Correct:
const ref = useRef<TurnstileInstance>(null)
<button onClick={() => ref.current?.execute()}>Verify</button>
// Must set execution and appearance modes
<Turnstile
ref={ref}
siteKey="xxx"
options={{
execution: 'execute',
appearance: 'execute'
}}
/>