Help people enter, review, and submit information without losing their work.
A form collects related information for a defined outcome. Begin with the information needed for that outcome, then group fields in an order people can understand. Explain what will happen when they submit and what remains editable afterward.
Use a native <form> for submission. Liquidify Form renders a layout group, not an HTML form: its sections organize content but do not validate, submit, or save it. Place it inside a native form when its grouping is useful.
Choose TextField for short text and TextEditor for longer input. Use Picker or Toggle for appropriate value choices. Connect state through each component's documented callback; a text field supplies the next string rather than a DOM change event.
Give inputs names when using native form data. Do not assume every value control contributes a native form field: for custom controls, serialize the application's controlled state explicitly. Use Button type="submit" for submission; the default Button type is button.
Figure in preparation
Forms: interaction sequence
Validation and persistence have different failure paths.
This example saves through the form handler. Its saveName function is supplied by the application and must reject when saving fails. In a React Server Components framework, place it behind a client boundary and supply a client-callable operation.
import { useId, useState, type FormEvent } from "react"
import { Button, TextField } from "@liquidify/react"
import "@liquidify/react/styles.css"
export function NameForm({ saveName }: { saveName: (name: string) => Promise<void> }) {
const fieldId = useId()
const [name, setName] = useState("")
const [pending, setPending] = useState(false)
const [message, setMessage] = useState("")
async function submit(event: FormEvent<HTMLFormElement>) {
event.preventDefault()
if (pending) return
setPending(true)
setMessage("")
try {
await saveName(name)
setMessage("Name saved.")
} catch {
setMessage("Your name could not be saved. Your entry is still here. Try again.")
} finally {
setPending(false)
}
}
return (
<form onSubmit={submit}>
<label htmlFor={fieldId}>Display name</label>
<TextField
id={fieldId}
name="displayName"
value={name}
onChange={setName}
required
readOnly={pending}
/>
<Button type="submit" loading={pending}>
{pending ? "Saving name" : "Save name"}
</Button>
<p role="status">{message}</p>
</form>
)
}Native required catches an empty value before submission. Application and service validation still determine what is acceptable. The read-only field preserves the submitted value while waiting. Do not also attach saving to Button onPress or TextField onSubmit: TextField's Enter callback does not suppress native form submission, so that combination can send the operation twice.
For field-specific errors, connect visible error text through aria-describedby or aria-errormessage and set invalid on the affected TextField or TextEditor. These props expose state; they do not run validation. For multiple errors, provide a summary with links to the fields and choose a deliberate focus destination after submission.
Keep recoverable values after a failed save. Separate a server failure from invalid input so people do not edit a correct value unnecessarily. If the result of a request is unknown, reconcile it before offering a retry that could create duplicate records.
Cancel should have a defined effect on the draft. Do not silently erase edits when collapsing a section or navigating between steps. See Dialogs when abandoning work warrants a decision; its action callbacks do not await saves.
Use persistent visible labels, concise instructions, and specific error messages. Placeholder text is a hint, not a replacement for a label. State requirements before people submit.
Keep a logical Tab order and support Enter submission without duplicating handlers. Verify labels, errors, pending announcements, and focus after failure. Review Disabled states before making submission unavailable: people still need a way to discover what is missing.
Use the Form catalog for section composition and TextField for native attributes. Read Read-only states and Loading for the states used here.