Mugo Web main content.

Making Google reCAPTCHA v2 play nice with browser form validation

By: Benjamin Kroll | October 31, 2024 | development and Web accessibility

CAPTCHA is an essential need on online forms, but to be blunt, the UX sucks. Without the implementation tips (helpfully detailed below), Google’s otherwise reliable reCAPTCHA service implemented “as-is” doesn’t actually provide any browser validation. The user will have to wait for it to make a time-consuming round trip to the server. It’s a problem for anyone and becomes compounded for users with accessibility needs.

The background: CAPTCHA is a necessity

Adding forms to a website without some kind of CAPTCHA solution these days is asking for trouble.

Spam bots crawl the web hoping to submit their messages to anything or anyone with an open ear, email account, or form. And while CAPTCHA is not 100 percent effective at blocking advanced bots, it offers a solid baseline to protect against crawlers that can’t check that “yes, I am a human” box. 

Visual and audio challenges are fine in most cases, but CAPTCHA tools present impassable barriers to some users. In fact, some government agencies advise finding workarounds, and the W3C has published detailed guidelines about the best use of CAPTCHA. Developers continue to refine CAPTCHA functionality, including innovations like “invisible” mode – which analyzes user behavior before presenting a challenge – in Google’s popular reCAPTCHA v2 tool.

But, unfortunately, the suggested implementation for Google reCAPTCHA gets in the way of native browser functionality, which can be detrimental to your site’s accessibility. 

In this post, we’ll examine two approaches using Google’s reCAPTCHA v2 solution – the default implementation and Mugo Web’s custom modification. You’ll see that our approach lets Google and the browser’s form validations play nice to create a secure and accessible user experience.

The problem: Google’s default skips useful validation feedback for users

One default Google reCAPTCHA v2 "invisible" implementation looks something like this:

<html>   <head> <title>reCAPTCHA demo: Example A</title>   <script src="https://www.google.com/recaptcha/api.js" async defer></script>   <script>       function onSubmit(token) {           document.getElementById("demo-form").submit();       }   </script>   </head>   <body> <form id="demo-form" action="/example_a" method="POST">         <label for="name">Name:</label><input type="text" id="name" name="name" required />         <label for="email">Email:</label><input type="email" id="email" name="email" required />      <button class="g-recaptcha" data-sitekey="your_site_key" data-callback="onSubmit">Submit</button> </form>   </body> </html>

In this implementation, after the user clicks the submit button, instead of the default submit event, the called reCAPTCHA script dispatches a click event to be used in reCAPTCHA’s ongoing automated Turing test evaluation.

If the observed behavior pattern is deemed to not be particularly “human”, reCAPTCHA may present a challenge modal. If the evaluation is successful or the user passes the challenge, the form’s onSubmit callback function is triggered, which submits the form.
The issue with this flow is that the browser-based HTML form validation is bypassed entirely, due to reCAPTCHA hijacking the submit button events and limitations of .submit() in the callback.

That native validation, however, is important for usability. It allows accessibility tools to provide additional context information which helps to avoid frustration or confusion as a result of missing or invalid form entries.

The fix: customizing reCAPTCHA v2 to fit more naturally in the user flow

A better approach is to invoke the reCAPTCHA check programmatically, as we have in the following example. While this code uses jQuery, it is not required. The same results can be accomplished with pure JavaScript.

<html>   <head> <title>reCAPTCHA demo: Example B</title>   <script src="https://www.google.com/recaptcha/api.js" async defer></script>       <script src="jquery.js"></script>       <script src="mugo-custom-form.js"></script>   <script>         function onReCaptchaCompleted() {           $('#demo-form').submit();         }       </script>   </head>   <body> <form id="demo-form" action="/example_b" method="POST">         <label for="name">Name:</label><input type="text" id="name" name="name" required />         <label for="email">Email:</label><input type="email" id="email" name="email" required />      <div id="mugo-custom-form-recaptcha" class="g-recaptcha" data-sitekey="your_site_key" data-callback="onReCaptchaCompleted" data-size="invisible"></div>      <button id="mugo-custom-form-submit" class="button" type="submit">Submit</button> </form>   </body> </html>

 # in mugo-custom-form.js $( '#demo-form' ).submit(function( e ) {   e.preventDefault();     // reCAPTCHA in use ...   if ( $( '.g-recaptcha' ).length )   {     // but not yet completed     if ( !grecaptcha.getResponse() )     {       // trigger the captcha 1       grecaptcha.execute();         return false;     }   }   #... further processing, AJAX calls etc

Unlike in the default implementation, the browser’s native HTML form validation triggers first when the user clicks the submit button. This allows users to fix any issues before a potentially slow or confusing round trip to the server for validation.

Once HTML form validation is passed, the form’s submit event is dispatched, which is picked up by a custom submit handler. The handler checks if the reCAPTCHA element is present, to ensure checks are only run when needed.

If an “invisible”  reCAPTCHA response is not yet available, a manual evaluation is triggered and the form is not submitted. Note that if more than one reCAPTCHA protected form is present, the form’s g-recaptcha-reponse-id needs to be supplied as the argument for grecaptcha.execute().

If a response is available, however, the onReCaptchaCompleted callback on the form page is triggered. It submits the form again, and any further processing can take place. The evaluation of the reCAPTCHA response is handled on the server side and not shown here.

Making security and accessibility work hand-in-hand

reCAPTCHA is a great tool, and Google continues to improve its feature set. But no tool is perfect, and some of reCAPTCHA’s default implementations interrupt useful browser-based form validation. With some customization, you can make reCAPTCHA fit more seamlessly into user flows to ensure your site is both secure and accessible.