# react-hcaptcha is a thin React wrapper around the hCaptcha JS API

> @hcaptcha/react-hcaptcha is an MIT licensed TypeScript component library that mounts an hCaptcha widget inside a React or Preact form, offers a ref for programmatic execution and a provider with a hook, and ships both ES module and CommonJS builds with a separate hooks entry point. Version 2.2.1 was released on 2026-10-13, and the library is small enough that the honest comparison is with writing the same wrapper yourself.

**hCaptcha/react-hcaptcha** — hCaptcha Component Library for ReactJS and Preact

- Repository: https://github.com/hCaptcha/react-hcaptcha
- Stars: 648 · Forks: 249
- Language: TypeScript
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/hcaptcha-react-hcaptcha

## The component mounts the widget and appends the hCaptcha API to its parent

The library does one job, and the README is explicit about the mechanism. The component automatically includes and loads the hCaptcha API library and appends it to the parent component. There is no script tag to add to your index.html and no global to initialise, because the wrapper takes care of injecting the API into the element you render it in.

That design has an immediate consequence for how you use it. The two requirements are the sitekey prop and a parent component such as a form. The component is rendered inside your own markup, and it attaches the API to the surrounding element rather than to document.body, which matters when you have several widgets on a page or when your layout controls overflow and stacking.

The props are a thin pass-through to the hCaptcha API. The standard form of the component takes a sitekey and an onVerify callback that receives the token and an encryption key:

```js
import HCaptcha from '@hcaptcha/react-hcaptcha';

<FormComponent>
    <HCaptcha
      sitekey="your-sitekey"
      onVerify={(token,ekey) => handleVerificationSuccess(token, ekey)}
    />
</FormComponent>
```

Optional props cover size, with normal, compact and invisible as the values and normal as the default, the theme, which accepts light, dark or a custom object, a tabindex for keyboard ordering, and a languageOverride that takes an ISO 639-2 code while the underlying API auto-detects the language from the browser by default.

One prerequisite is worth stating plainly: a sitekey is required, and the README tells you to sign up at hCaptcha to get one. The component cannot render without it, so the account and key come before the code does.

## Three integration styles cover standard, programmatic and provider usage

The README documents three ways to wire the widget, and they differ in where the token ends up.

The standard style renders the widget and reacts to onVerify. The programmatic style exists for the case where you want to call the hCaptcha client API directly, and it works through a ref plus an onLoad callback. Waiting for onLoad matters, because the README explains that by the time it fires the hCaptcha API is ready and the client has been set up. Only then does calling execute on the ref reach the real API rather than a client that has not finished initialising.

The third style is the provider and hook pattern, where a HCaptchaProvider is placed higher in the tree with the sitekey and a child component calls useHCaptcha to read state and drive the widget. The provider is the closest thing to a context-based version of the same component, and it is the pattern that scales when more than one form needs the captcha.

The distinction between the styles is mostly about who triggers the challenge. With the standard style the user clicks the widget. With the programmatic style your code triggers it, typically on submit. With the provider style your code triggers it through the hook. For a sign-up form where you want to challenge only after validation passes, the programmatic style is the one that fits, and the ref pattern described for form libraries is the variant of it.

## The hook returns ready, token, executeInstance and resetInstance

The provider pattern is defined by what the hook hands back, and the README lists those properties precisely: executeInstance, a function that executes hCaptcha programmatically and returns a promise resolving with a new token; resetInstance, which resets the hCaptcha client instance; ready, a boolean stating whether hCaptcha is loaded and ready; token, the current token as a string or null; error, an error object or null; and sitekey, the sitekey in use.

The shape of the returned object is what makes the pattern usable in a real form. A submit button can be disabled while ready is false, which avoids the common bug of a user pressing submit before the API has loaded and receiving nothing back. executeInstance returns a promise, so the token can be awaited inside an async submit handler rather than delivered to a callback. resetInstance is the counterpart that a form needs after a failed validation, since a solved captcha is normally consumed once.

The provider takes the same sitekey requirement as the component, plus size, theme, rqdata, languageOverride, onVerify and onError. The rqdata prop is pointed at the enterprise documentation, which is where the business specific payload for those accounts is described.

TypeScript support is handled with a small indirection. The README notes that if you want to reassign the component name, you can import it in a utility file and re-export it as the default, which gives you a typed name of your own choosing.

## challenge-container renders the puzzle inline instead of in a popup

The most recent feature in the README is custom challenge containers, and it changes where the interaction happens. Passing challenge-container with a container ID or a DOM element makes hCaptcha render the puzzle inline rather than using its own popup or shim. The container has to be mounted before hCaptcha renders, which the example shows by placing the div ahead of the component:

```jsx
<div id="captcha-challenge" />
<HCaptcha
  ref={captchaRef}
  sitekey={sitekey}
  size="invisible"
  challenge-container="captcha-challenge"
/>
```

The behaviour that comes with it is worth understanding before you ship it. Clicks outside the container leave the inline puzzle open, and execute({ async: true }) keeps waiting for the result rather than resolving early. You keep control of placement and of any surrounding modal or backdrop, and the demo_cc folder in the repository is described as a dimmed modal with configurable dismissal, which is the reference for that pattern.

For a checkout or signup flow where the popup would cover the form, this is the option that keeps the challenge on the same screen as the fields. The trade is that you now own the dismissal behaviour, since the library no longer decides when the puzzle closes.

## Enterprise payloads travel through the execute call

Passing an object into the execute call sends it through to the underlying JS API, and the README ties this to enterprise features such as rqdata. The example builds a payload object and only fills in the rqdata key when the prop is present:

```js
const {sitekey, rqdata} = props;
const captchaRef = React.useRef<HCaptcha>(null);

const onLoad = () => {
  const executePayload = {};
  if (rqdata) {
    executePayload['rqdata'] = rqdata;
  }
  captchaRef.current?.execute(executePayload);
};

return <HCaptcha ref={captchaRef} onLoad={onLoad} sitekey={sitekey} {...props} />;
```

Two habits from the example are worth copying. The payload is built conditionally so that the key is absent rather than empty when the feature is not in use, and the rest of the props are spread onto the component so that a caller can pass extra options through. The optional chaining on the ref access matters in practice, because a ref can still be null if the component has not mounted when onLoad fires.

The rqdata prop is a string on the component and on the provider, and the README points at the enterprise documentation for its contents. Nothing in the library validates it, so the value has to be produced and signed elsewhere and passed in already formed.

## The package ships ESM, CommonJS and a separate hooks entry point

The publishing setup is more elaborate than the component itself, and it tells you something about how the library is maintained. The manifest points at types/index.d.ts for the type definitions, lib/dist/cjs/index.js for the CommonJS build and lib/dist/esm/index.js for the module build:

```json
  "types": "types/index.d.ts",
  "main": "lib/dist/cjs/index.js",
  "module": "lib/dist/esm/index.js",
```

The exports map adds a second entry point for hooks, with its own type declaration at types/hooks/index.d.ts and matching ESM and CommonJS files, and the typesVersions field maps the hooks types for consumers on older resolution rules. The published files are limited to README.md, lib/dist and types, with tsbuildinfo excluded.

Development is a pnpm workspace. The root package.json delegates to filtered packages with scripts such as pnpm install --prefer-offline followed by a filtered compile step, and the start script runs the library compile in watch mode alongside the demo. The repository contains a demo folder with its own package.json, a webpack config and an .swcrc, plus the demo_cc folder for the custom challenge container work. The pnpm-lock.yaml and pnpm-workspace.yaml at the root are what tie those together.

For a consumer this mostly means one npm install and an import. The dual build matters for bundler configurations that still resolve CommonJS, and the separate hooks path lets an application that only wants the provider and hook avoid pulling in the default component.

## What the wrapper does not cover

This is a rendering library, and the boundary of what it does is easy to misjudge from the name. It mounts the widget, loads the hCaptcha API, and hands you a token. Everything else around a captcha stays outside: the sitekey and the account behind it, the meaning of the token, and any check you perform on your own server.

The integration surface is also narrower than it may look. The README documents the size, theme, tabindex, languageOverride, rqdata, onVerify, onError, onLoad and ref behaviour, and it mentions that the library works with Preact, but it does not describe accessibility options, a headless or test mode, or any built-in way to render the challenge without a visible widget other than the invisible size and the custom container. If your requirement list includes those, you will be reading the hCaptcha API documentation directly.

The honest alternative is to write the wrapper yourself. Against the hCaptcha JS API directly, a small amount of code loads the script, creates the client, and calls execute and reset, and you get exactly the behaviour you need with no dependency to keep current. The reason to prefer the library is that it already handles the parts that are fiddly and easy to get subtly wrong: attaching the API to the parent component, waiting for the client to be ready before executing, and shipping the same logic to both React and Preact consumers. The reason to prefer your own code is that a wrapper cannot give you server side verification, and the token means nothing until your backend checks it.

Maintenance on the library itself is steady rather than busy. The last push was on 2026-10-20, three days after version 2.2.1 was published on 2026-10-13, with v2.2.0 on 2026-08-25 and 2.1.0 on 2026-08-05. The licence is MIT, whose main condition for commercial use is keeping the copyright notice, though that is a description of the terms rather than legal advice.

## Conclusion

Adopt react-hcaptcha if you are adding hCaptcha to a React or Preact form and want the widget mounted, the token captured and the API loaded without hand wiring the script tag. Do not adopt it for a non React frontend, for an invisible challenge that needs a server side verify step you will build yourself, or if you need features the README does not describe such as accessibility options or a headless test mode. Verify first that the sitekey you hold matches the domain you are serving, and read the challenge-container example in the demo_cc folder before you assume the default popup placement will fit your layout.

## FAQ

### How do I install and use react-hcaptcha in a React form?

Run npm install @hcaptcha/react-hcaptcha --save, sign up at hCaptcha for a sitekey, then render the component inside a form element with the sitekey prop and an onVerify callback. The component loads the hCaptcha API itself and appends it to the parent component, so no script tag is needed.

### How do I trigger the hCaptcha challenge programmatically?

Keep a ref to the component, wait for the onLoad callback so that the API is ready, and call execute on the ref, optionally with a payload object for features such as rqdata. With the provider and hook pattern, the useHCaptcha hook returns an executeInstance function that returns a promise resolving with a new token.

### Can I show the hCaptcha puzzle inline instead of in a popup?

Yes. Pass challenge-container with a container ID or DOM element and render that container before the component mounts, and the puzzle is drawn inline rather than in the default popup. You then control placement and dismissal, and the demo_cc folder in the repository shows a dimmed modal variant.

## Sources

- [hCaptcha/react-hcaptcha on GitHub](https://github.com/hCaptcha/react-hcaptcha)
- [Issues](https://github.com/hCaptcha/react-hcaptcha/issues)
- [License: MIT](https://github.com/hCaptcha/react-hcaptcha/blob/master/LICENSE)
- [README](https://github.com/hCaptcha/react-hcaptcha/blob/master/README.md)
- [Releases](https://github.com/hCaptcha/react-hcaptcha/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/hcaptcha-react-hcaptcha
