Your visitors are tired of typing into boxes. Let their AI do it. In Next.js you load the script with next/script and tell it which form to use. It works with controlled inputs. I tested this in a real Next.js app, with the production build.
The snippet
Make it a client component. Wrap your form in a div. Give the form an id.
"use client";
import Script from "next/script";
import { useRef, useState } from "react";
declare global {
interface Window {
AgentFill?: {
mount: (
form: HTMLFormElement,
options?: { title?: string; formId?: string; buttonLabel?: string },
) => void;
};
}
}
export function ContactForm() {
const formRef = useRef<HTMLFormElement>(null);
const [name, setName] = useState("");
return (
<div>
<form
ref={formRef}
id="contact"
onSubmit={(e) => {
e.preventDefault();
// send your data here
}}
>
<label htmlFor="name">Your name</label>
<input id="name" name="name" value={name} onChange={(e) => setName(e.target.value)} required />
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" required />
<label htmlFor="message">Message</label>
<textarea id="message" name="message" />
<button type="submit">Send</button>
</form>
<Script
src="https://aiformapi.com/agent-fill.js"
data-form="#contact"
data-title="Contact us"
onReady={() => {
if (formRef.current) window.AgentFill?.mount(formRef.current, { title: "Contact us" });
}}
/>
</div>
);
}Why each part is there
data-formanddata-titlework the first time the script loads. The script finds#contactand mounts it.onReadyruns every time the component mounts again, for example after your visitor goes to another page and comes back.next/scriptdoes not load the script a second time, so the mount call is what brings the button back. A form is mounted once, so the two ways never double up.- The wrapping
divmatters. The script puts its button right before your form in the page. When React leaves the page, it removes the div and the button goes with it. Without the div, I saw a leftover button stay behind and a second one appear on return. - A form with an id keeps the prompt clear. The id becomes the form id in the answer block.
Plain React without Next.js
Add <script src="https://aiformapi.com/agent-fill.js"></script> to your index.html before your app bundle. Then call window.AgentFill.mount(formRef.current) inside useEffect.
Check it
Open the page, press the button, and read the prompt. Paste a JSON answer and watch your state update. Go to another page and back. You should see one button.
What I checked
I built a Next.js app with a search form in the layout, which is the usual case. I loaded the script three ways. The data-form way and the onReady way both gave a button on first load, and onReady gave it again after client-side navigation. A controlled input took the pasted value and its state updated. I read the next/script documentation for the loading rules.
Sources (read 2026-10-30)
Questions
Does it work with controlled inputs?
Yes. The script sets values the way a person types, with input and change events, so React sees each change and your state updates. I tested this with a controlled input in a real Next.js app.
Why did I get two buttons after I left the page and came back?
The script puts its button next to your form in the page. If React removes only the form, the button stays behind. Put the form inside a div of its own. React then removes the button with the div. I tested both versions.
Why does my custom title not show in the prompt?
If the page has exactly one form, a script that loads after the page, as next/script loads it, mounts that form on its own with the default options. A later mount call is ignored. Set data-title and data-button on the Script tag as well, as in the snippet.