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.
Widget Customization
Customize the Turnstile widget's appearance, behavior, and integration with your application's UX.
Appearance Modes
Control when the widget is visible to users:
always (default)
Widget is always visible.
<Turnstile
siteKey="YOUR_SITE_KEY"
options={{ appearance: 'always' }}
/>
execute
Widget 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-only
Widget only appears when user interaction is required (most privacy-friendly).
<Turnstile
siteKey="YOUR_SITE_KEY"
options={{ appearance: 'interaction-only' }}
/>
Execution Modes
Control when the token is generated:
render (default)
Token is generated automatically when the widget renders.
execute
Token is generated only when you explicitly call execute(). Useful for delaying verification.
Themes
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' }} />
Language/Localization
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/
Dynamic Callbacks
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!
Manual Script Injection
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}
/>
</>
)
}
Custom Container Element
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" />
Common Mistakes
❌ Forgetting useCallback with rerenderOnCallbackChange
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}
/>
❌ Calling execute() Without Proper Setup
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'
}}
/>
See Also
- basic-setup skill - Getting started
- token-lifecycle skill - Handling tokens and forms
- nextjs-ssr skill - Next.js integration patterns