Add a contact form to your Remix app without writing a custom action. FormsList handles submissions so you can skip Remix action loaders entirely.
TL;DR
To add a contact form to Remix without a custom action, submit form data via fetch to `https://formslist.com/f/your-form-id` from a client-side handler. FormsList processes submissions so you don't need a Remix action loader.
Sign up for a free FormsList account and create a new form. Copy the endpoint URL from your dashboard.
// Your endpoint will look like this:
// https://formslist.com/f/YOUR_FORM_HASHCreate a new route file in your Remix app. Instead of using a Remix action, we handle submission client-side with fetch. This keeps the form simple and avoids the need for a server-side action function.
// app/routes/contact.tsx
import { useState } from "react";
import type { MetaFunction } from "@remix-run/node";
export const meta: MetaFunction = () => {
return [
{ title: "Contact Us" },
{ name: "description", content: "Get in touch with us." },
];
};
export default function Contact() {
const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus("sending");
const form = e.currentTarget;
const data = new FormData(form);
try {
const res = await fetch("https://formslist.com/f/YOUR_FORM_HASH", {
method: "POST",
body: data,
});
if (res.ok) {
setStatus("sent");
form.reset();
} else {
setStatus("error");
}
} catch {
setStatus("error");
}
}
return (
<main className="mx-auto max-w-2xl py-12 px-4">
<h1 className="text-3xl font-bold mb-6">Contact Us</h1>
<form onSubmit={handleSubmit} className="space-y-4 max-w-md">
<div>
<label htmlFor="name" className="block text-sm font-medium">Name</label>
<input
type="text"
id="name"
name="name"
required
className="mt-1 block w-full rounded border p-2"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium">Email</label>
<input
type="email"
id="email"
name="email"
required
className="mt-1 block w-full rounded border p-2"
/>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium">Message</label>
<textarea
id="message"
name="message"
rows={4}
required
className="mt-1 block w-full rounded border p-2"
/>
</div>
<button
type="submit"
disabled={status === "sending"}
className="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{status === "sending" ? "Sending..." : "Send Message"}
</button>
{status === "sent" && <p className="text-green-600">Message sent successfully!</p>}
{status === "error" && <p className="text-red-600">Something went wrong. Please try again.</p>}
</form>
</main>
);
}Link to the contact page from your site navigation or any other page using Remix's Link component.
import { Link } from "@remix-run/react";
<Link to="/contact">Contact Us</Link>Run `npm run dev`, navigate to /contact, and submit a test message. Verify it appears in your FormsList dashboard. Enable email notifications, auto-responders, or integrations from the dashboard.
You can, but it adds complexity: you would need a server-side action function, validation, and email sending logic. With FormsList, a simple client-side fetch handles everything without any server code.
Yes. You can set the form's action attribute to your FormsList endpoint and method to POST as a fallback. The client-side fetch handler provides a better UX when JavaScript is available, and the native form submission works as a fallback.
Yes. Since the form submission goes directly to FormsList from the client, it works regardless of where your Remix app is deployed: Vercel, Cloudflare Workers, Fly.io, or any other platform.
200 free submissions a month and a live endpoint in 30 seconds. No server, no credit card, just point your form at it.