Lend a form's fields
An assisted field is a field on an assisted page the assistant fills as the user would type into it. Each field is a tool of its name, and the page’s note shows what each field says now. Render it inside an AssistedPage. Outside one, it throws.
A fill changes the form, not your data. Keep saving on the page’s own button, lent as an action.
Wrap a field
Section titled “Wrap a field”Wrap the input, textarea, native select, checkbox or switch in AssistedInput with a name and a label. The label says what the field holds. From examples/app/app/orders/[id]/page.tsx:
<AssistedInput name="note" label="The order's note"> <Textarea value={draft} onChange={(event) => setDraft(event.target.value)} /></AssistedInput>AssistedInput takes one child and attaches a ref to it. The child must pass that ref to the field’s DOM element. shadcn’s Input, Textarea and NativeSelect do.
A fill writes through the element’s own value setter and fires an input event, so your onChange runs as if the user typed. Uncontrolled forms see the new value too. Once the page has rendered, the fill answers Filled "<label>"; it now says <value>.
Group a form’s fields by id
Section titled “Group a form’s fields by id”Give every field the same name and its own id. The assistant sees one tool that fills a field by id. From examples/app/app/settings/page.tsx:
{fields.map(([field, id, label]) => ( <Label key={field} className="grid gap-1.5"> {label} <AssistedInput name="setting" id={id} label={label}> <Input value={draft[field]} onChange={(event) => set(field, event.target.value)} /> </AssistedInput> </Label>))}The page’s note then lists the group with each field’s value:
Its fields: setting (name: Shop name, now "Northwind"; currency: Currency, now "USD"; email: Notification email, now "alerts@northwind.example").Say in the page’s instructions which action saves the form, so the assistant fills and then saves.
Know what each field takes
Section titled “Know what each field takes”AssistedInput reads the wrapped element when it mounts and fills it by its kind:
| Child | A fill | The note shows |
|---|---|---|
<input>, <textarea> |
Sets the value, fires input |
The text, cut at 200 characters |
<input type="password"> |
Sets the value, fires input |
(filled) or (empty) |
Checkbox, or role="switch" |
Clicks it when it is not already on or off as asked |
on or off |
<select> |
Sets the value, fires change |
The selected value |
| Anything else | Rings it for the user | Its text |
A select’s options become the only values its tool takes, read from the DOM when the field mounts. A switch or checkbox takes on and off.
Leave a field to the user
Section titled “Leave a field to the user”Pass consequential when the user should type the value themselves. A call then rings the field and answers that filling it is theirs:
<AssistedInput name="email" label="Your email" consequential> <Input type="email" name="email" /></AssistedInput>A disabled or read-only field answers that it is disabled or read-only right now, and nothing changes.
Lend a control that is not a native field
Section titled “Lend a control that is not a native field”A custom control keeps its value in state, not in a DOM field. Lend it with AssistedField, which takes the value and a function that fills it. options lists the only values it takes:
const plans = [ { value: "free", label: "Free" }, { value: "pro", label: "Pro" },];
export function PlanPicker({ plan, setPlan }: { plan: string; setPlan: (plan: string) => void }) { return ( <AssistedField name="plan" label="Billing plan" options={plans} value={plan} onFill={setPlan}> <div role="radiogroup" aria-label="Billing plan"> {plans.map((option) => ( <button key={option.value} type="button" role="radio" aria-checked={plan === option.value} onClick={() => setPlan(option.value)} > {option.label} </button> ))} </div> </AssistedField> );}The child is only the ring target. onFill gets the value the assistant chose, and the answer shows value once the page has rendered.
Use the hook instead
Section titled “Use the hook instead”When wrapping does not fit, call useAssistedInput and attach the ref it returns:
export function CouponInput() { const ref = useAssistedInput<HTMLInputElement>({ name: "coupon", label: "Coupon code" }); return <input ref={ref} name="coupon" />;}Check it
Section titled “Check it”Open the devtools’ Assisted pages tab. The Fields list shows each field tool with what every field says now. See Mount the devtools.