Capítulo 91 de 108
Build accessible forms in Next.js with <Field /> components, useActionState and Server Actions — client and server-side validation without a form library.
useActionState: React hook returning [formState, formAction, pending]; drives form state, submission and pending status.<Form action={formAction}>: Next.js <Form /> from next/form enables navigation + progressive enhancement."use server" function that receives (prevState, formData), validates with Zod safeParse(), returns { values, errors, success }.data-invalid / aria-invalid: Set on <Field> and input respectively from !!formState.errors?.field?.length to trigger error styling and a11y.data-disabled: Set on <Field> from pending to style disabled state during submission.values from the server action's success return; React resets the form to initial state automatically.values alongside errors so user input isn't lost on validation failure."use server"
export async function bugReportFormAction(
_prevState: FormState,
formData: FormData
) {
const values = {
title: formData.get("title") as string,
description: formData.get("description") as string,
}
const result = formSchema.safeParse(values)
if (!result.success) {
return { values, success: false, errors: result.error.flatten().fieldErrors }
}
return { errors: null, success: true }
}
safeParse), retorno de values no erro (preserva input) e omissão no sucesso (reseta form).<Field data-invalid={!!formState.errors?.email?.length}>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" name="email" type="email" aria-invalid={!!formState.errors?.email?.length} />
{formState.errors?.email && <FieldError>{formState.errors.email[0]}</FieldError>}
</Field>
<FieldError />, sincronizando data-invalid (estilo) e aria-invalid (a11y).values no erro: o form reseta e o usuário perde o que digitou; sempre retorne values junto de errors.useActionState + Server Actions substitui react-hook-form para casos simples/médios em Next.js, mantendo tudo server-first.FormState devem viver em arquivo separado (schema.ts) para serem importados tanto no client quanto no server action.pending do useActionState cobre loading (Spinner) e disabled de inputs/botão simultaneamente.data-invalid/aria-invalid por <FieldSet>/<Field>.<Field>, <FieldGroup>, <FieldSet>, <FieldLegend> são a base de markup usada aqui.useActionState é API do React 19, exige Next.js 15+/React 19.