Form Integration

Make the v0 contact form send for real

v0 draws a tidy shadcn/ui form and then, more often than not, finishes it with a pretend submit. Swap that for your Formbold endpoint. The design stays, the placeholder goes, and each entry is stored and emailed, in the v0 preview and after you publish to Vercel.

Contact Form for v0

What does the finished v0 component look like?

A client component using the shadcn/ui pieces v0 already generated. The endpoint comes from NEXT_PUBLIC_FORMBOLD_ENDPOINT if you set it. Full walkthrough: /docs/v0-contact-form.

v0.tsx
"use client";
// components/contact-section.tsx  (Next.js + shadcn/ui, the way v0 scaffolds it)
import { useState, type FormEvent } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";

const endpoint =
  process.env.NEXT_PUBLIC_FORMBOLD_ENDPOINT ?? "https://formbold.com/s/FORM_ID";

export default function ContactSection() {
  const [state, setState] = useState<"ready" | "posting" | "ok" | "fail">("ready");

  async function submit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const el = e.currentTarget;
    setState("posting");
    try {
      const r = await fetch(endpoint, {
        method: "POST",
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
        },
        body: JSON.stringify(Object.fromEntries(new FormData(el))),
      });
      if (!r.ok) throw new Error(String(r.status));
      el.reset();
      setState("ok");
    } catch {
      setState("fail");
    }
  }

  return (
    <form onSubmit={submit} className="grid gap-4 max-w-md">
      <div className="grid gap-2">
        <Label htmlFor="cs-email">Work email</Label>
        <Input id="cs-email" name="email" type="email" required />
      </div>
      <div className="grid gap-2">
        <Label htmlFor="cs-company">Company</Label>
        <Input id="cs-company" name="company" />
      </div>
      <div className="grid gap-2">
        <Label htmlFor="cs-msg">How can we help?</Label>
        <Textarea id="cs-msg" name="message" required />
      </div>
      <Button type="submit" disabled={state === "posting"}>
        {state === "posting" ? "Sending..." : "Talk to us"}
      </Button>
      {state === "ok" && <p className="text-sm text-green-600">Thanks. A real person reads every message.</p>}
      {state === "fail" && <p className="text-sm text-destructive">Sending failed. Email us directly instead.</p>}
    </form>
  );
}
How it works

Endpoint, prompt, publish

One prompt does most of the work. Read the diff before you publish.

1

Make the Formbold form

Add a form in the Formbold dashboard. The Form Setup tab shows its address, which ends in /s/FORM_ID.

2

Tell v0 what to change

"Turn the contact section into a client component and send it to Formbold. POST the fields as JSON to process.env.NEXT_PUBLIC_FORMBOLD_ENDPOINT, falling back to https://formbold.com/s/FORM_ID, with Accept: application/json. Keep the shadcn/ui Input, Textarea, Label and Button, keep a name on each field, and add ready, posting, ok and fail states. Delete any mock delay or unused Server Action."

3

Publish to Vercel

Click Publish in the chat header. The first publish creates the Vercel project, and later edits go out with Publish Changes. Submit once on the live URL.

Features

What v0 leaves out

What v0 leaves out of a contact form, and what fills the gap.

No route or action to keep

There is no app/api file or Server Action to maintain. The component posts directly.

Same behavior on a static export

Because the request runs in the browser, the form keeps working if you later move to output: export.

Readable emails

Each submission emails you with labels taken from your field names, like email, company and message.

A captcha that works with fetch

Basic Spam Filter is one switch. If you want a captcha too, Custom reCAPTCHA with your own keys is the option built for script submits.

Webhooks for your own stack

On Starter and up, a webhook can POST each entry as JSON to another route, a queue or an automation tool.

FAQs

Preview and env var questions

Previews, environment variables and leftover Server Actions.

Project menu, then Settings, then Environment Variables. v0 previews only read Development values, so add it there as well as Production.

Still have a question?

The docs cover setup, framework guides, integrations and spam protection, step by step.