Overview
By default, Google reCAPTCHA renders a badge in the bottom-right corner of your page. With custom container rendering, you can:
Control exactly where the badge appears
Render the badge inline within your UI
Apply custom styling and theming
Manage multiple reCAPTCHA instances
How It Works
When you provide a container prop to GoogleReCaptchaProvider, the library:
Uses explicit rendering mode instead of automatic rendering
Calls grecaptcha.render() with your specified element
Applies custom parameters like badge position and theme
Returns a client ID that’s used for token generation
Source reference: src/google-recaptcha-provider.tsx:98-104
Basic Usage
Create a container element
Add a div with a unique ID where you want the badge to appear: function App () {
return (
< div >
< h1 > My Application </ h1 >
{ /* Badge will render here */ }
< div id = "recaptcha-container" />
</ div >
);
}
Configure the provider
Pass the container configuration to GoogleReCaptchaProvider: import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3' ;
function App () {
return (
< GoogleReCaptchaProvider
reCaptchaKey = "YOUR_SITE_KEY"
container = { {
element: 'recaptcha-container' ,
parameters: {
badge: 'inline'
}
} }
>
< div >
< h1 > My Application </ h1 >
< div id = "recaptcha-container" />
</ div >
</ GoogleReCaptchaProvider >
);
}
Use executeRecaptcha normally
Token generation works the same way: import { useGoogleReCaptcha } from 'react-google-recaptcha-v3' ;
function MyComponent () {
const { executeRecaptcha } = useGoogleReCaptcha ();
const handleAction = async () => {
if ( ! executeRecaptcha ) return ;
const token = await executeRecaptcha ( 'my_action' );
// Use token...
};
return < button onClick = { handleAction } > Submit </ button > ;
}
Container Configuration
Element Options
The element property accepts two types:
container.element
string | HTMLElement
required
String : ID of the container element (without #)
HTMLElement : Direct reference to a DOM element
Using String ID
Using HTMLElement Reference
< GoogleReCaptchaProvider
reCaptchaKey = "YOUR_SITE_KEY"
container = { {
element: 'my-recaptcha-badge' , // ID without '#'
parameters: { badge: 'inline' }
} }
>
< div id = "my-recaptcha-badge" />
</ GoogleReCaptchaProvider >
Parameter Options
Configuration object for the reCAPTCHA badge appearance and behavior.
container.parameters.badge
'inline' | 'bottomright' | 'bottomleft'
Position of the reCAPTCHA badge:
inline: Renders inside your container element
bottomright: Fixed position in bottom-right corner (default)
bottomleft: Fixed position in bottom-left corner
container.parameters.theme
Visual theme of the badge. Defaults to light.
container.parameters.tabindex
Tab index for keyboard navigation.
container.parameters.callback
Function called when the user submits a successful response.
container.parameters.expiredCallback
Function called when the response expires and needs revalidation.
container.parameters.errorCallback
Function called when an error occurs during verification.
Common Patterns
Render the badge inline within your footer:
function App () {
return (
< GoogleReCaptchaProvider
reCaptchaKey = "YOUR_SITE_KEY"
container = { {
element: 'footer-recaptcha' ,
parameters: {
badge: 'inline' ,
theme: 'light'
}
} }
>
< main >
{ /* Your app content */ }
</ main >
< footer >
< p > Protected by reCAPTCHA </ p >
< div id = "footer-recaptcha" />
</ footer >
</ GoogleReCaptchaProvider >
);
}
Dark Theme Badge
Match the badge to a dark-themed UI:
< GoogleReCaptchaProvider
reCaptchaKey = "YOUR_SITE_KEY"
container = { {
element: 'dark-badge' ,
parameters: {
badge: 'inline' ,
theme: 'dark'
}
} }
>
< div className = "dark-mode" >
< div id = "dark-badge" />
</ div >
</ GoogleReCaptchaProvider >
Badge in Modal or Dialog
Render the badge inside a modal:
function LoginModal ({ isOpen }) {
if ( ! isOpen ) return null ;
return (
< div className = "modal" >
< h2 > Login </ h2 >
< form >
< input type = "email" placeholder = "Email" />
< input type = "password" placeholder = "Password" />
< button type = "submit" > Login </ button >
</ form >
{ /* Badge appears at bottom of modal */ }
< div id = "modal-recaptcha" />
</ div >
);
}
function App () {
return (
< GoogleReCaptchaProvider
reCaptchaKey = "YOUR_SITE_KEY"
container = { {
element: 'modal-recaptcha' ,
parameters: {
badge: 'inline' ,
theme: 'light'
}
} }
>
< LoginModal isOpen = { true } />
</ GoogleReCaptchaProvider >
);
}
Custom Positioning with CSS
Style the container element to position the badge exactly where you want:
function App () {
return (
< GoogleReCaptchaProvider
reCaptchaKey = "YOUR_SITE_KEY"
container = { {
element: 'custom-badge' ,
parameters: {
badge: 'inline'
}
} }
>
< div
id = "custom-badge"
style = { {
position: 'fixed' ,
bottom: '20px' ,
left: '20px' ,
zIndex: 1000
} }
/>
</ GoogleReCaptchaProvider >
);
}
Advanced: Lifecycle Callbacks
Monitor badge interactions with callback functions:
function App () {
const handleCallback = () => {
console . log ( 'User submitted a response' );
};
const handleExpired = () => {
console . log ( 'Response expired, needs revalidation' );
};
const handleError = () => {
console . error ( 'reCAPTCHA encountered an error' );
};
return (
< GoogleReCaptchaProvider
reCaptchaKey = "YOUR_SITE_KEY"
container = { {
element: 'monitored-badge' ,
parameters: {
badge: 'inline' ,
callback: handleCallback ,
expiredCallback: handleExpired ,
errorCallback: handleError
}
} }
>
< div id = "monitored-badge" />
</ GoogleReCaptchaProvider >
);
}
Make sure callback functions are stable references (use useCallback) to prevent unnecessary re-renders of the provider.
Styling the Badge
The badge is rendered in an iframe, which limits direct styling. However, you can:
Control Container Size
The badge has a fixed size, but you can control its container:
#recaptcha-container {
width : 100 % ;
display : flex ;
justify-content : center ;
padding : 20 px ;
background-color : #f5f5f5 ;
border-radius : 8 px ;
}
Hide Badge (with caution)
Google’s terms of service require the reCAPTCHA badge to be visible unless you include specific disclosure text. See Google’s FAQ for details.
If you include the required disclosure, you can hide the badge:
.grecaptcha-badge {
visibility : hidden ;
}
Required disclosure text:
< p >
This site is protected by reCAPTCHA and the Google
< a href = "https://policies.google.com/privacy" > Privacy Policy </ a > and
< a href = "https://policies.google.com/terms" > Terms of Service </ a > apply.
</ p >
Troubleshooting
Common causes :
Container element doesn’t exist when provider mounts
Element ID is incorrect or includes #
Script hasn’t finished loading
Solutions :// Ensure container exists before provider
< GoogleReCaptchaProvider
reCaptchaKey = "YOUR_SITE_KEY"
container = { {
element: 'my-badge' , // No '#' prefix
parameters: { badge: 'inline' }
} }
>
{ /* Container must be rendered */ }
< div id = "my-badge" />
</ GoogleReCaptchaProvider >
Badge appears in wrong location
Cause : Using bottomright or bottomleft instead of inline.Solution : Set badge: 'inline' in parameters:container = {{
element : 'my-badge' ,
parameters : {
badge : 'inline' // Not 'bottomright'
}
}}
Cause : Provider is re-rendering and creating duplicate badges.Solution :
Ensure provider is placed high in your component tree
Check that container element ID is unique
Verify provider isn’t being remounted unnecessarily
The library automatically cleans up on unmount (see src/utils.ts:110-125)
Badge doesn't match theme
Cause : Theme parameter not set or being overridden.Solution :container = {{
element : 'my-badge' ,
parameters : {
badge : 'inline' ,
theme : 'dark' // Explicitly set theme
}
}}
Complete Example
Here’s a full example with custom container, dark theme, and lifecycle callbacks:
import { useState , useCallback } from 'react' ;
import {
GoogleReCaptchaProvider ,
useGoogleReCaptcha
} from 'react-google-recaptcha-v3' ;
function ContactForm () {
const { executeRecaptcha } = useGoogleReCaptcha ();
const [ status , setStatus ] = useState ( '' );
const handleSubmit = useCallback ( async ( e ) => {
e . preventDefault ();
if ( ! executeRecaptcha ) {
setStatus ( 'reCAPTCHA not ready' );
return ;
}
try {
const token = await executeRecaptcha ( 'contact_form' );
const response = await fetch ( '/api/contact' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ token })
});
if ( response . ok ) {
setStatus ( 'Message sent successfully!' );
}
} catch ( error ) {
setStatus ( 'Error sending message' );
}
}, [ executeRecaptcha ]);
return (
< form onSubmit = { handleSubmit } >
< input type = "text" placeholder = "Name" required />
< input type = "email" placeholder = "Email" required />
< textarea placeholder = "Message" required />
< button type = "submit" > Send Message </ button >
{ status && < p > { status } </ p > }
</ form >
);
}
function App () {
const handleBadgeCallback = useCallback (() => {
console . log ( 'Badge interaction detected' );
}, []);
const handleBadgeExpired = useCallback (() => {
console . warn ( 'Badge expired, will refresh on next action' );
}, []);
const handleBadgeError = useCallback (() => {
console . error ( 'reCAPTCHA error occurred' );
}, []);
return (
< GoogleReCaptchaProvider
reCaptchaKey = { process . env . REACT_APP_RECAPTCHA_KEY }
container = { {
element: 'recaptcha-badge' ,
parameters: {
badge: 'inline' ,
theme: 'dark' ,
tabindex: 0 ,
callback: handleBadgeCallback ,
expiredCallback: handleBadgeExpired ,
errorCallback: handleBadgeError
}
} }
>
< div className = "app dark-theme" >
< h1 > Contact Us </ h1 >
< ContactForm />
< footer >
< p > Protected by reCAPTCHA </ p >
< div id = "recaptcha-badge" />
</ footer >
</ div >
</ GoogleReCaptchaProvider >
);
}
export default App ;
Best Practices
Place container early Render the container element before the provider mounts to avoid timing issues.
Use inline for control Set badge: 'inline' when you want full control over badge placement.
Respect Google's ToS Keep the badge visible or include required disclosure text if hiding it.
Stable callbacks Wrap callback functions in useCallback to prevent unnecessary re-renders.