On this page

Guides

TanStack Form

Wire Luke UI fields to TanStack Form with form.Field.

TanStack Form owns the form state, and Luke UI renders the controls.

Initialise the form

Call useForm with defaultValues and a submit handler. revalidateLogic holds validation back until the first submit, then revalidates each field as it changes.

const schema = z.object({
	email: z.email('Enter a valid email address.'),
	name: z.string().min(1, 'Enter your name.'),
});

const form = useForm({
	defaultValues: { email: '', name: '' },
	onSubmit: ({ value }) => saveAccount(value),
	validationLogic: revalidateLogic({ mode: 'submit', modeAfterSubmission: 'change' }),
	validators: { onDynamic: schema, onSubmit: schema },
});

TanStack Form accepts any Standard Schema validator, so Zod needs no resolver package. The form's value types come from defaultValues, and TypeScript checks the schema against them.

Integrate components

Wrap the fields in Luke UI's Form. Set validationBehavior="aria" so TanStack Form controls validation without browser submission blocking it. When every required field should show a clear marker, set necessityIndicator="required-label".

Give form.Field a name and a children function. Pass field.state.value, field.handleChange, and field.handleBlur to the control, and pass field.name as the HTML name. Set isRequired on fields the schema requires. Schema validation does not set required control semantics on its own.

Pass the current validation message from field.state.meta.errors to errorMessage. A non-empty message marks the field invalid.

TanStack Form: Text fields
Loading TanStack Form: Text fields example

A checkbox or switch reads its value from isSelected.

TanStack Form: Checkbox
Loading TanStack Form: Checkbox example

Read Validation for more about error presentation. Read Required fields for necessity indicators.

Focus the first invalid field

TanStack Form leaves focus where it is after a failed submission. Hold a ref to the Form element and search it for the first control marked aria-invalid from onSubmitInvalid.

const FOCUSABLE_SELECTOR =
	'input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])';

function focusFirstInvalidField(form: HTMLFormElement | null) {
	const invalid = form?.querySelector('[aria-invalid="true"]');
	if (!invalid) return;
	const control = invalid.matches(FOCUSABLE_SELECTOR)
		? invalid
		: invalid.querySelector(FOCUSABLE_SELECTOR);
	if (control instanceof HTMLElement) control.focus();
}

The first branch focuses an invalid control directly. The second branch finds a control inside an invalid group or wrapper.

Check the component's API when you need a ref to one specific control. ref reaches the component's root. A prop such as inputRef or triggerRef reaches the named descendant. For example, TextInputField.inputRef reaches the input, and SelectField.triggerRef reaches the trigger button. A primitive that renders the control itself takes a plain ref.

Submitting data

Prevent the browser's default submission in Form's onSubmit, then call form.handleSubmit(). TanStack Form validates the values and calls onSubmit or onSubmitInvalid. Prefer onSubmit over Form's action for this integration, because the library needs the submit event.

Continue learning