Documentation · itzsa

Captcha

Company-standard canvas captcha with ref + onVerified, attempt limits, and structured API error props. Client friction only — pair with a server check for sensitive flows.

@itzsa/captcharegistry → components/itzsa/captcha

Live demo#

Same API as production — try charset mode, length, and a simulated bad API response.

Letters & numbers — match case exactly as shown

0/6 · 5 left

onVerifiedfalse · validate()false

Installation#

npm package or copy-paste from the itzsa registry.

pnpm add @itzsa/captcha
$ pnpm dlx shadcn@latest add https://itzsa.acharya-suman.com.np/r/captcha.json

Getting started#

Minimal usage is enough for most forms.

Minimal — ref + onVerified#

Yes: this pattern is fully supported. onVerified(true) when the answer is accepted; onVerified(false) on wrong input, refresh, or clear.

import { useRef, useState } from "react";
import { Captcha, type CaptchaHandle } from "@itzsa/captcha";

export function LoginGate() {
  const captchaRef = useRef<CaptchaHandle>(null);
  const [verified, setVerified] = useState(false);

  function handleCaptchaVerified(valid: boolean) {
    setVerified(valid);
  }

  function onSubmit() {
    // Imperative check before submit
    if (!captchaRef.current?.validate()) return;
    // …submit form
  }

  return (
    <>
      <Captcha ref={captchaRef} onVerified={handleCaptchaVerified} />
      <button type="button" disabled={!verified} onClick={onSubmit}>
        Continue
      </button>
    </>
  );
}

Checklist

  • onVerified updates UI (enable submit).
  • captchaRef.current?.validate() before submit.
  • Optional: captchaRef.current?.refresh() after a failed host API.

Server verify / API errors#

Pass verify for async checks. Throw or return false on bad API calls — onError receives a CaptchaError. Use error for host-driven messages.

const [apiError, setApiError] = useState<string | null>(null);

<Captcha
  ref={captchaRef}
  error={apiError}
  maxAttempts={5}
  verify={async ({ value, challengeId }) => {
    const res = await fetch("/api/captcha/verify", {
      method: "POST",
      body: JSON.stringify({ value, challengeId }),
    });
    if (!res.ok) throw new Error("verify_failed"); // → onError + status "error"
    return true;
  }}
  onError={(err) => console.warn(err.code, err.message)}
  onVerified={handleCaptchaVerified}
/>

Props#

Public surface of Captcha. Tables use a solid white background.

Core#

Core

PropTypeDefaultDescription
length / charsnumber6Number of characters in the challenge (3–16).
charsetMode"both" | "letters" | "numbers""both"Letters + digits, letters only, or digits only.
excludeAmbiguousbooleantrueDrop look-alikes (0 / O / 1 / l / I).
caseSensitivebooleantrueExact case match (ignored when charsetMode is numbers).
theme"light" | "dark" | "system""system"Canvas color scheme.
noisenumber0.55Interference intensity from 0 to 1.
maxAttemptsnumber5Failures before status becomes locked.
onVerified(valid: boolean) => void-Called with true when the answer is accepted, false when cleared / wrong / refreshed. Works with only ref + onVerified.

Verify & errors#

Verify & errors

PropTypeDefaultDescription
verify(payload) => boolean | Promise<boolean>-Optional server check. Return false or throw on a bad API call.
verifyTimeoutMsnumber15000Abort verify() after this many milliseconds.
errorstring | null-Controlled host/API error (e.g. login 429). Shown under the field.
loadingboolean-Controlled loading while a host API is in flight.
onError(error: CaptchaError) => void-Structured failures: invalid, verify_failed, network, timeout, max_attempts, …
onLock(error: CaptchaError) => void-Fired when maxAttempts is reached.
autoRefreshOnInvalidbooleanfalseIssue a new challenge after a wrong answer.
autoRefreshOnErrorbooleanfalseIssue a new challenge after a verify/API failure.

Chrome & styling#

Chrome

PropTypeDefaultDescription
label / showLabel / requiredstring / boolean / boolean"Security check" / true / falseAccessible field label and required marker.
messagesCaptchaMessages-Override placeholders and status copy.
showRefresh / showCounter / showStatusbooleantrueToggle chrome pieces.
className / canvasClassName / inputClassName / …string-Styling hooks for root, canvas, input, refresh, label, error.
value / defaultValue / disabled / id / name-Form control helpers.

Imperative API#

CaptchaHandle via ref — same object as captchaRef.current.

CaptchaHandle

PropTypeDefaultDescription
refresh()() => void-New challenge, clear input, reset attempts.
reset()() => void-Clear input without regenerating the challenge.
validate()() => boolean-True if currently valid (or local match).
getValue()() => string-Current user input.
getChallengeId()() => string-Opaque id for this challenge (server correlation).
getStatus() / getAttempts()() => CaptchaStatus / number-Latest status and failure count.
unlock()(opts?) => void-Clear lock; refreshes by default.

Registry#

Installs under components/itzsa/captcha (nested components/ui).

captcha.json

https://itzsa.acharya-suman.com.np/r/captcha.json