Skip to main content

StructuredOutputForm

A model that extracts or proposes structured data is usually almost right. Rather than trusting the JSON, show it as a form: each field becomes the matching control (Input, NumberInput, Switch, NativeSelect) inside a Field, so labels, descriptions and ids are wired, and the human fixes what is wrong before it is used. Fields the model filled carry a Generated badge until they are edited.

import StructuredOutputForm, {type StructuredField} from '@zuilib/ai/structured-output-form'

Basic

Loading example

Owner had no value from the model, so it has no badge; the others lose theirs on the first edit and onFieldChange fires once per field.

Which fields are generated

By default every field that has a value when the form mounts counts as generated. Pass generated={['seats', 'term']} to say exactly which, for instance when some values were pre-filled by your own code.

Errors

Validation is yours (the model's output, your schema). Pass errors={{seats: 'Must be at least 10'}} and the matching field renders the message under its control as a FieldError (data-slot="structured-output-form-error") and is marked invalid through Field (a boolean field sets aria-invalid and aria-describedby on the switch itself). The submit button stays enabled so the human can fix and resubmit.

Below sm (640px) the buttons stack full width; on a coarse pointer every control has a 44px target.

Props

PropTypeDefaultDescription
fieldsrequiredStructuredField[]{name, label, type, options?, description?, required?, placeholder?}
valuerequiredRecord<string, string | number | boolean | null | undefined>
onValueChangerequired(value, changedField: string) => void
onSubmit(value) => void
generatedstring[]Defaults to every field with a value on mount
onFieldChange(name: string) => voidFirst edit of a generated field
errorsPartial<Record<string, ReactNode>>Per-field validation messages; a field with one is invalid
renderField(props: StructuredFieldControlProps) => ReactNode | undefinedCalled for every field first; return a control to take it over (label, badge, description and error stay wired), or undefined to keep the built-in
submitLabelstring'Use these values'
generatedLabelstring'Generated'
footerReactNodeBefore the submit button
showSubmitbooleantrue
classNamestring

type is a built-in — 'string' | 'number' | 'boolean' | 'enum' — or any name a renderField handles ('date', 'currency'); an unknown type with no custom control falls back to the text input, so a schema from a newer producer stays editable. An enum's options are strings or {value, label} pairs.