Overview
React Turnstile supports rendering multiple widget instances on the same page. Each widget operates independently with its own state and callbacks.
Simply render multiple <Turnstile> components with unique id props:
import { Turnstile } from '@marsidev/react-turnstile'
function MultipleWidgets () {
return (
< div >
< h2 > Form 1 </ h2 >
< Turnstile
id = "widget-1"
siteKey = "1x00000000000000000000AA"
onSuccess = { ( token ) => console . log ( 'Widget 1:' , token ) }
/>
< h2 > Form 2 </ h2 >
< Turnstile
id = "widget-2"
siteKey = "1x00000000000000000000AA"
onSuccess = { ( token ) => console . log ( 'Widget 2:' , token ) }
/>
</ div >
)
}
Each widget must have a unique id prop to ensure proper rendering and cleanup.
Managing Multiple Tokens
Track tokens from multiple widgets:
import { Turnstile } from '@marsidev/react-turnstile'
import { useState } from 'react'
function MultipleFormsPage () {
const [ tokens , setTokens ] = useState < Record < string , string >>({})
const handleSuccess = ( widgetId : string ) => ( token : string ) => {
setTokens ( prev => ({ ... prev , [widgetId]: token }))
}
const submitForm1 = () => {
if ( ! tokens [ 'widget-1' ]) {
alert ( 'Please complete verification for Form 1' )
return
}
// Submit form 1 with tokens['widget-1']
}
const submitForm2 = () => {
if ( ! tokens [ 'widget-2' ]) {
alert ( 'Please complete verification for Form 2' )
return
}
// Submit form 2 with tokens['widget-2']
}
return (
< div >
< form onSubmit = { ( e ) => { e . preventDefault (); submitForm1 () } } >
< h2 > Login Form </ h2 >
< input type = "email" placeholder = "Email" />
< input type = "password" placeholder = "Password" />
< Turnstile
id = "widget-1"
siteKey = "1x00000000000000000000AA"
onSuccess = { handleSuccess ( 'widget-1' ) }
/>
< button type = "submit" disabled = { ! tokens [ 'widget-1' ] } >
Login
</ button >
</ form >
< form onSubmit = { ( e ) => { e . preventDefault (); submitForm2 () } } >
< h2 > Signup Form </ h2 >
< input type = "email" placeholder = "Email" />
< input type = "password" placeholder = "Password" />
< Turnstile
id = "widget-2"
siteKey = "1x00000000000000000000AA"
onSuccess = { handleSuccess ( 'widget-2' ) }
/>
< button type = "submit" disabled = { ! tokens [ 'widget-2' ] } >
Sign Up
</ button >
</ form >
</ div >
)
}
Control multiple widgets programmatically:
import { Turnstile , TurnstileInstance } from '@marsidev/react-turnstile'
import { useRef } from 'react'
function WidgetControls () {
const widget1Ref = useRef < TurnstileInstance >( null )
const widget2Ref = useRef < TurnstileInstance >( null )
const resetAll = () => {
widget1Ref . current ?. reset ()
widget2Ref . current ?. reset ()
}
const getTokens = async () => {
const token1 = widget1Ref . current ?. getResponse ()
const token2 = widget2Ref . current ?. getResponse ()
console . log ({ token1 , token2 })
}
return (
< div >
< Turnstile
ref = { widget1Ref }
id = "widget-1"
siteKey = "1x00000000000000000000AA"
/>
< Turnstile
ref = { widget2Ref }
id = "widget-2"
siteKey = "1x00000000000000000000AA"
/>
< button onClick = { resetAll } > Reset All Widgets </ button >
< button onClick = { getTokens } > Get All Tokens </ button >
</ div >
)
}
Different Configurations
Each widget can have different settings:
< div >
{ /* Invisible widget for automated forms */ }
< Turnstile
id = "invisible-widget"
siteKey = "1x00000000000000000000AA"
options = { {
size: 'invisible' ,
execution: 'execute'
} }
/>
{ /* Visible compact widget */ }
< Turnstile
id = "compact-widget"
siteKey = "1x00000000000000000000AA"
options = { {
size: 'compact' ,
theme: 'dark'
} }
/>
{ /* Normal widget with custom language */ }
< Turnstile
id = "normal-widget"
siteKey = "1x00000000000000000000AA"
options = { {
size: 'normal' ,
language: 'es'
} }
/>
</ div >
By default, the Turnstile script is injected automatically and shared across all widgets. You only need to inject it once:
function App () {
return (
< div >
{ /* First widget injects the script */ }
< Turnstile
id = "widget-1"
siteKey = "1x00000000000000000000AA"
injectScript = { true }
/>
{ /* Subsequent widgets reuse the same script */ }
< Turnstile
id = "widget-2"
siteKey = "1x00000000000000000000AA"
injectScript = { false }
/>
</ div >
)
}
The script is automatically shared, so you can set injectScript={true} on all widgets or just the first one. The library handles script loading efficiently.
Conditional Rendering
Handle widgets that are conditionally rendered:
import { Turnstile } from '@marsidev/react-turnstile'
import { useState } from 'react'
function ConditionalWidgets () {
const [ showWidget1 , setShowWidget1 ] = useState ( true )
const [ showWidget2 , setShowWidget2 ] = useState ( false )
return (
< div >
< button onClick = { () => setShowWidget1 ( ! showWidget1 ) } >
Toggle Widget 1
</ button >
< button onClick = { () => setShowWidget2 ( ! showWidget2 ) } >
Toggle Widget 2
</ button >
{ showWidget1 && (
< Turnstile
id = "widget-1"
siteKey = "1x00000000000000000000AA"
/>
) }
{ showWidget2 && (
< Turnstile
id = "widget-2"
siteKey = "1x00000000000000000000AA"
/>
) }
</ div >
)
}
When a widget is unmounted, itβs automatically cleaned up. If you remount it, a new widget instance will be created.
Best Practices
Unique IDs Always provide a unique id prop for each widget instance
Script Management Let the first widget inject the script, others will reuse it
Token Tracking Use an object or Map to track tokens by widget ID
Cleanup Widgets are automatically cleaned up on unmount
Next Steps
Interact with Widget Learn about imperative widget control
Manual Script Injection Take control of script loading