بنقرة واحدة
multiple-widgets
// 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.
// 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.
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.
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.
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.
| name | multiple-widgets |
| description | 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. |
| triggers | ["multiple turnstile widgets","two turnstile forms","turnstile widget conflict","turnstile race condition","turnstile id","multiple captcha same page","widget not responding"] |
| category | core |
| metadata | {"library":"@marsidev/react-turnstile","library_version":"1.4.2","framework":"React"} |
Handle multiple Turnstile widgets on the same page without conflicts or race conditions.
When using multiple widgets, always provide unique IDs:
import { Turnstile } from '@marsidev/react-turnstile'
export default function MultiFormPage() {
return (
<>
<section>
<h2>Newsletter Signup</h2>
<form>
<input type="email" placeholder="Email" />
<Turnstile
id="newsletter-widget"
siteKey="YOUR_SITE_KEY"
/>
<button type="submit">Subscribe</button>
</form>
</section>
<section>
<h2>Contact Form</h2>
<form>
<input type="email" placeholder="Email" />
<textarea placeholder="Message" />
<Turnstile
id="contact-widget"
siteKey="YOUR_SITE_KEY"
/>
<button type="submit">Send</button>
</form>
</section>
</>
)
}
Control each widget independently:
import { Turnstile } from '@marsidev/react-turnstile'
import type { TurnstileInstance } from '@marsidev/react-turnstile'
import { useRef } from 'react'
export default function MultiFormPage() {
const newsletterRef = useRef<TurnstileInstance>(null)
const contactRef = useRef<TurnstileInstance>(null)
async function handleNewsletterSubmit(e: React.FormEvent) {
e.preventDefault()
const token = newsletterRef.current?.getResponse()
if (!token) {
alert('Please complete the CAPTCHA')
return
}
// Submit form...
// Reset only this widget
newsletterRef.current?.reset()
}
async function handleContactSubmit(e: React.FormEvent) {
e.preventDefault()
const token = contactRef.current?.getResponse()
if (!token) {
alert('Please complete the CAPTCHA')
return
}
// Submit form...
// Reset only this widget
contactRef.current?.reset()
}
return (
<>
<section>
<h2>Newsletter Signup</h2>
<form onSubmit={handleNewsletterSubmit}>
<input type="email" placeholder="Email" />
<Turnstile
ref={newsletterRef}
id="newsletter-widget"
siteKey="YOUR_SITE_KEY"
/>
<button type="submit">Subscribe</button>
</form>
</section>
<section>
<h2>Contact Form</h2>
<form onSubmit={handleContactSubmit}>
<input type="email" placeholder="Email" />
<textarea placeholder="Message" />
<Turnstile
ref={contactRef}
id="contact-widget"
siteKey="YOUR_SITE_KEY"
/>
<button type="submit">Send</button>
</form>
</section>
</>
)
}
When using multiple widgets, manual script injection ensures optimal loading:
import {
Turnstile,
SCRIPT_URL,
DEFAULT_SCRIPT_ID
} from '@marsidev/react-turnstile'
import Script from 'next/script'
export default function MultiFormPage() {
return (
<>
{/* Single script for all widgets */}
<Script
id={DEFAULT_SCRIPT_ID}
src={SCRIPT_URL}
strategy="afterInteractive"
/>
<section>
<h2>Form 1</h2>
<Turnstile
id="widget-1"
siteKey="YOUR_SITE_KEY"
injectScript={false}
/>
</section>
<section>
<h2>Form 2</h2>
<Turnstile
id="widget-2"
siteKey="YOUR_SITE_KEY"
injectScript={false}
/>
</section>
</>
)
}
When widgets appear/disappear based on state:
import { Turnstile } from '@marsidev/react-turnstile'
import type { TurnstileInstance } from '@marsidev/react-turnstile'
import { useRef, useState } from 'react'
export default function DynamicForms() {
const [showNewsletter, setShowNewsletter] = useState(false)
const [showContact, setShowContact] = useState(false)
const newsletterRef = useRef<TurnstileInstance>(null)
const contactRef = useRef<TurnstileInstance>(null)
return (
<>
<button onClick={() => setShowNewsletter(!showNewsletter)}>
Toggle Newsletter
</button>
<button onClick={() => setShowContact(!showContact)}>
Toggle Contact
</button>
{showNewsletter && (
<section>
<h2>Newsletter</h2>
<Turnstile
ref={newsletterRef}
id="newsletter-widget"
siteKey="YOUR_SITE_KEY"
/>
</section>
)}
{showContact && (
<section>
<h2>Contact</h2>
<Turnstile
ref={contactRef}
id="contact-widget"
siteKey="YOUR_SITE_KEY"
/>
</section>
)}
</>
)
}
Multiple forms in tabs:
import { Turnstile } from '@marsidev/react-turnstile'
import type { TurnstileInstance } from '@marsidev/react-turnstile'
import { useRef, useState } from 'react'
export default function TabbedForms() {
const [activeTab, setActiveTab] = useState<'login' | 'signup'>('login')
const loginRef = useRef<TurnstileInstance>(null)
const signupRef = useRef<TurnstileInstance>(null)
return (
<>
<div role="tablist">
<button onClick={() => setActiveTab('login')}>Login</button>
<button onClick={() => setActiveTab('signup')}>Sign Up</button>
</div>
{activeTab === 'login' && (
<form>
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
<Turnstile
ref={loginRef}
id="login-widget"
siteKey="YOUR_SITE_KEY"
/>
<button type="submit">Login</button>
</form>
)}
{activeTab === 'signup' && (
<form>
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
<input type="password" placeholder="Confirm Password" />
<Turnstile
ref={signupRef}
id="signup-widget"
siteKey="YOUR_SITE_KEY"
/>
<button type="submit">Sign Up</button>
</form>
)}
</>
)
}
Wrong:
<>
<Turnstile siteKey="xxx" /> // Uses default "cf-turnstile" id
<Turnstile siteKey="xxx" /> // Also uses "cf-turnstile" id - CONFLICT!
</>
Result: Widgets interfere with each other, only one works correctly.
Correct:
<>
<Turnstile id="widget-1" siteKey="xxx" />
<Turnstile id="widget-2" siteKey="xxx" />
</>
Wrong:
// Each widget tries to inject the script independently
<Turnstile siteKey="xxx" />
<Turnstile siteKey="xxx" />
Result: Race conditions, widgets may fail to initialize.
Correct:
// Single script injection for all widgets
<Script id={DEFAULT_SCRIPT_ID} src={SCRIPT_URL} />
<Turnstile id="widget-1" siteKey="xxx" injectScript={false} />
<Turnstile id="widget-2" siteKey="xxx" injectScript={false} />
Wrong:
const ref = useRef<TurnstileInstance>(null)
<>
<Turnstile ref={ref} id="widget-1" siteKey="xxx" />
<Turnstile ref={ref} id="widget-2" siteKey="xxx" /> // ❌ Same ref!
</>
Result: Both widgets share state, methods don't work correctly.
Correct:
const ref1 = useRef<TurnstileInstance>(null)
const ref2 = useRef<TurnstileInstance>(null)
<>
<Turnstile ref={ref1} id="widget-1" siteKey="xxx" />
<Turnstile ref={ref2} id="widget-2" siteKey="xxx" />
</>
If widgets aren't working correctly:
id propsinjectScript={false}