Integration guide
Build an accessible React form with an explicit honeypot, predictable pending state, useful errors, and a submission path that matches your security model.
Step by step
Use a real form and named inputs so FormData, browser validation, keyboard submission, and assistive technology work normally.
Keep it inside the form but outside the visible layout. aria-hidden removes the wrapper from the accessibility tree; tabIndex prevents keyboard focus.
Use new FormData(event.currentTarget). Do not manually set Content-Type because the browser supplies the multipart boundary.
Disable repeat submission while pending, announce errors with role=alert, and reset only after response.ok.
Direct browser delivery is simplest and relies on allowed origins, captcha, and rate limiting. A React framework server route can keep the API key private and must validate the honeypot before proxying.
Verify success, a deliberately filled honeypot rejection, captcha behavior, and notification/webhook delivery from the real hostname.
Honeypot in this integration
Render the honeypot as an uncontrolled text input inside the React form. Use the exact FormDex field name, tabIndex={-1}, autoComplete="off", and an off-screen wrapper. Do not store or populate its value in component state; FormData includes it automatically as an empty string.
Read the honeypot fundamentals →Working example
import { useState } from "react";
export function ContactForm() {
const [status, setStatus] = useState("idle");
const [error, setError] = useState("");
async function handleSubmit(event) {
event.preventDefault();
const form = event.currentTarget;
setStatus("submitting");
setError("");
try {
const response = await fetch("https://YOUR_FORMDEX_DOMAIN/api/forms/FORM_ID", {
method: "POST",
headers: { "X-API-Key": "YOUR_API_KEY" },
body: new FormData(form)
});
const result = await response.json();
if (!response.ok) throw new Error(result.message || result.error || "Submission failed");
form.reset();
setStatus("success");
} catch (requestError) {
setError(requestError.message);
setStatus("error");
}
}
return (
<form onSubmit={handleSubmit}>
{/* HONEYPOT: exact FormDex field name; keep empty and off-screen. */}
<div aria-hidden="true" style={{ position: "absolute", left: "-10000px", width: 1, height: 1, overflow: "hidden" }}>
<label>Leave this field empty
<input type="text" name="company" tabIndex={-1} autoComplete="off" />
</label>
</div>
<label>Email <input type="email" name="email" required /></label>
<label>Message <textarea name="message" required /></label>
<label>Attachments <input type="file" name="attachments" accept=".pdf,.jpg,.jpeg,.png" multiple /></label>
{error ? <p role="alert">{error}</p> : null}
{status === "success" ? <p role="status">Enquiry sent.</p> : null}
<button type="submit" disabled={status === "submitting"}>
{status === "submitting" ? "Sending…" : "Send enquiry"}
</button>
</form>
);
}Troubleshooting
Honeypot always triggers
Remove controlled values, placeholders, and autofill from the honeypot input; it must submit an empty string.
CORS error
Add the deployed React origin exactly, including scheme and port where applicable.
State updates but no submission
Confirm the handler uses the form’s onSubmit event and the button has type=submit.
Captcha failure
Render the configured provider widget and obtain a fresh token for every attempt.