Start here. This is the direct spoken answer to practice first.
Overview
A good form remains understandable before hydration, during a request, after expected errors, and after success.
I submit a semantic form to a Server Action and use useActionState for the action's structured result. A submit component can read useFormStatus to show pending state and prevent accidental duplicate intent. The server validates the FormData and returns field or form errors as expected data, while a successful action redirects or returns a clear success state.