Skip to content

Frontend Widget ​

The widget is a standard Web Component (<cap-widget>), loaded with a single <script> tag and usable with any framework.

Setup ​

html
<script src="https://capcat.ai/widget/cap.js"></script>

TIP

This script is hosted by Capcat itself on capcat.ai, with no third-party CDN dependency, so it loads reliably worldwide (including China).

Form mode (zero JavaScript) ​

When the widget is placed inside a <form>, it automatically injects a hidden cap-token field and submits it along with the form:

html
<form action="/submit" method="POST">
  <cap-widget data-cap-api-endpoint="https://api.capcat.ai/<site-key>/"></cap-widget>
  <button type="submit">Submit</button>
</form>

JavaScript mode (SPA / custom flow) ​

Listen for the solve event to get the token and decide yourself when to send it:

js
const widget = document.querySelector("cap-widget");
widget.addEventListener("solve", (e) => {
  const token = e.detail.token;
  // send the token to your backend, unlock the submit button, etc.
});

Common attributes ​

AttributeDescription
data-cap-api-endpointThe verification service URL, in the form https://api.capcat.ai/<site-key>/ (the site key is generated when you create a site in the console). Required.
data-cap-themeSet to auto to make the widget match your page's existing styles — see Auto theme.
data-cap-brandingSet to hidden to remove the Capcat badge in the widget's corner — see Hiding the badge.
data-cap-worker-countNumber of Web Workers used for solving, defaults to the device's core count.

Styling ​

The widget renders inside Shadow DOM, so your page's CSS can't accidentally break it. Customization goes through two supported channels — auto theme and --cap-* CSS variables — which can be freely combined.

Auto theme ​

Add data-cap-theme="auto" and the widget derives its look from the page around it, no CSS required:

html
<cap-widget data-cap-theme="auto" data-cap-api-endpoint="..."></cap-widget>

What gets sampled:

  • Font family, size & text color — inherited from the widget's position in the page; font size follows the reference input.
  • Background — the first opaque ancestor background, so it matches the card or panel it sits on and adapts to dark pages automatically.
  • Border color & radius — aligned with the first text input in the same <form>. Outside a form, the nearest surrounding form control is used, falling back to the nearest button (radius only, capped at 24px). The checkbox radius scales proportionally.
  • Accent — the form's accent-color if set, otherwise the text color (used for the spinner, focus ring and troubleshooting link).

Auto theme only fills in variables you haven't set yourself: any --cap-* variable declared in your CSS takes precedence, so you can combine auto with the manual overrides below. It re-samples when the OS color scheme flips; if your app switches themes at runtime (e.g. a class toggle), re-set the attribute (el.setAttribute("data-cap-theme", "auto")) after switching to re-sample.

CSS variables ​

Set any of these on the cap-widget element (values shown are the defaults):

css
cap-widget {
  --cap-background: #fdfdfd;
  --cap-border-color: #dddddd8f;
  --cap-border-radius: 14px;
  --cap-widget-width: 260px;
  --cap-widget-height: 58px;
  --cap-widget-padding: 14px;
  --cap-gap: 15px;
  --cap-color: #212121;
  --cap-font: system-ui, sans-serif;
  --cap-font-size: 15px;
  --cap-checkbox-size: 25px;
  --cap-checkbox-border: 1px solid #aaaaaad1;
  --cap-checkbox-border-radius: 6px;
  --cap-checkbox-background: #fafafa91;
  --cap-spinner-color: #000;
  --cap-spinner-background-color: #eee;
  --cap-focus-ring: #0066cc;
}

A common recipe — make the widget fill its container like other form fields:

css
cap-widget {
  display: block;
  --cap-widget-width: 100%;
}

Hiding the badge (paid plans) ​

data-cap-branding="hidden" removes the Capcat badge in the widget's corner. This is a paid-plan feature: enable Hide the Capcat badge for the site in the console first — on free sites the server makes the badge reappear.

Content Security Policy ​

If your pages send a Content-Security-Policy, the widget needs these allowances. Everything is served from capcat.ai / api.capcat.ai; no third-party CDN is contacted.

DirectiveValueWhy
script-srchttps://capcat.ai 'unsafe-eval' 'wasm-unsafe-eval', plus 'unsafe-inline' or a noncecap.js itself, the pako fallback script, compiling the solver WebAssembly module, and the instrumentation check that runs inside a sandboxed srcdoc iframe. That iframe inherits your page's CSP: its script is inline (hence 'unsafe-inline' or a nonce) and its environment checks use new Function / eval (hence 'unsafe-eval'). Without them the check fails silently and the widget reports an error after a 20 s timeout
connect-srchttps://api.capcat.ai https://capcat.aiChallenge/redeem API calls, and fetching cap_wasm_bg.*.wasm
worker-srcblob:Solver workers are created from a Blob URL (use child-src for browsers without worker-src)
style-src'unsafe-inline' or a nonceThe widget injects one <style> into its shadow root

A minimal policy that keeps everything else locked down:

Content-Security-Policy: default-src 'self';
  script-src 'self' https://capcat.ai 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval';
  connect-src 'self' https://api.capcat.ai https://capcat.ai;
  worker-src blob:;
  style-src 'self' 'unsafe-inline'

Nonces instead of 'unsafe-inline' ​

Set these globals before cap.js loads and the widget stamps your nonce onto everything it injects:

  • window.CAP_CSS_NONCE — the shadow-root <style> (lets you drop 'unsafe-inline' from style-src).
  • window.CAP_SCRIPT_NONCE — the inline script inside the instrumentation iframe and any provider script tags the widget adds (Turnstile / reCAPTCHA in Account Defense setups). Falls back to CAP_CSS_NONCE when unset. Keep 'unsafe-eval' in script-src either way; a nonce only replaces 'unsafe-inline'.
html
<script nonce="r4nd0m">
  window.CAP_CSS_NONCE = "r4nd0m";
  window.CAP_SCRIPT_NONCE = "r4nd0m";
</script>
<script src="https://capcat.ai/widget/cap.js" nonce="r4nd0m" async defer></script>

Hosting assets yourself ​

If your policy cannot allow capcat.ai for a given directive, point the widget at copies on your own origin and adjust the directives above accordingly:

  • window.CAP_CUSTOM_WASM_URL — absolute URL of cap_wasm_bg.wasm (download it from https://capcat.ai/widget/cap_wasm_bg.wasm).
  • window.CAP_PAKO_URL — URL of pako_inflate.min.js. The fallback is only loaded on browsers without DecompressionStream; the default copy is pinned with Subresource Integrity, a custom URL is loaded as-is.

Assign these globals from a script as plain strings. The widget ignores any other kind of value — including page elements that happen to share the name, such as <a id="CAP_CSS_NONCE"> in user-generated content — and only accepts asset URLs that are https: or on your page's own origin.

For React / Vue / Svelte integration examples, see the upstream Widget docs.

Built on Cap (Apache 2.0)