/examples/formslouise-toolkit/forms test mode · no data leaves your browserlouise-toolkit/forms turns a single field definition into the D1 table, the server + client validation, and the public capture route — no hand-rolled handler. The form on the left runs the same email + required checks the toolkit ships.
// The one definition — derives the table, review columns, and validation.
const contactForm = defineForm({
name: "inquiries",
fields: inquiriesForm.fields,
spam: { honeypot: "website", minSeconds: 2 },
});
// Wired into the worker's route list — public capture + editor-gated review:
formRoute({ form: contactForm, rateLimitKv: (env) => env.RL }),
inquiriesRoute({ table: inquiries, resolveEditor }),<form class="louise-demo-form grid gap-3" novalidate>
<label class="grid gap-1 text-sm">
<span class="text-base-content/70">Email</span>
<input
class="input input-bordered w-full"
type="email"
name="email"
value="jordan@acme"
required
/>
</label>
<label class="grid gap-1 text-sm">
<span class="text-base-content/70">Message</span>
<textarea
class="textarea textarea-bordered w-full"
name="message"
rows="3"
required>Loved the toolkit — a question about queues.</textarea>
</label>
<button class="btn btn-primary btn-sm w-fit" type="submit">Send inquiry</button>
<p class="louise-demo-status text-sm" role="status" aria-live="polite"></p>
</form>
<script>
// Mirror of louise-toolkit/forms `validateField` for the two built-in checks demoed
// here: `required`, and the `email` type's format rule. Kept deliberately tiny
// — the real engine (server + client) lives in louise-toolkit/forms/validate.ts.
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
document.querySelectorAll<HTMLFormElement>("form.louise-demo-form").forEach((form) => {
const status = form.querySelector<HTMLElement>(".louise-demo-status");
form.addEventListener("submit", (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(form).entries());
const email = String(data.email ?? "").trim();
const message = String(data.message ?? "").trim();
if (!email) return say("Email is required", "error");
if (!EMAIL_RE.test(email)) return say("Email must be a valid email address", "error");
if (!message) return say("Message is required", "error");
// Passed — show the exact payload the real route would validate + insert.
say(`Valid ✓ would POST → ${JSON.stringify({ email, message })}`, "ok");
function say(text: string, kind: "error" | "ok") {
if (!status) return;
status.textContent = text;
status.style.color =
kind === "ok" ? "var(--color-success)" : "var(--color-error)";
}
});
});
</script>jordan@acme.com and send — watch the same validation the server runs pass client-side. Deep-dive doc View sourceWant the fully wired version that actually stores a row and shows up in Louise Settings? That's the contact form on the home page — same inquiries definition, posting to its real capture route.