Cover Image

React form validation can start with the browser’s built-in constraints. Required fields and email syntax checks do not need a parallel implementation in React state. Use native checks for those rules, then add application logic and accessible feedback where the form needs more.
This guide shows how to let the browser handle the checks it already does well, how to read validity from an input element, and where React code is still needed. The rules come from MDN's form validation guide, MDN's reference for the required and email input types, React's reference for form inputs, and the W3C's Web Content Accessibility Guidelines (WCAG) 2.2 error-handling criteria. Choose the submission and error-display pattern that fits your React version and application.
Key Takeaways
Use native attributes such as
requiredandtype="email"first. Normal interactive submission checks them unless native validation is bypassed.Read validity from the input element, not from a parallel copy of the rules in React state.
Show each error next to its field, in text, and make sure it reaches screen readers.
Start with the browser's constraint validation
Browsers have validated forms for a long time. Mark a field as required and the browser will not submit an empty value. MDN lists the input types that support the attribute, including text, email, url, tel, password, number, date, checkbox, radio, and file, along with select and textarea (MDN, required attribute).
The email input type does the same for addresses. MDN states that the input value is automatically validated, so that it is either empty or a properly formatted email address before the form can be submitted. Passing values take the form username@domain or username@domain.tld, and MDN links to the full algorithm for anyone who needs the exact rule (MDN, input type email).
These attributes let the browser enforce common constraints without a separate JavaScript rule. During normal interactive submission, validation runs before the submit event. They also work on a phone keyboard, which can switch to an email layout when the type is set. For most forms, the first version of validation should be attributes, not state.
Check the browser's own rules before writing a custom check. Use a custom email rule only when the application requires a restriction beyond the browser’s syntax check. A syntactically valid email does not prove that the address exists or belongs to the user.
Read validity from the input element
MDN's form validation guide describes how an invalid element behaves. It matches the :invalid CSS pseudo-class. An empty required input is also invalid. A non-empty value that does not match a pattern is invalid. The guide describes these states on the element itself, which is where your code should look for them (MDN, client-side form validation).
Read the element’s validity flags and validation message when you need custom feedback. MDN’s checkValidity reference describes a Boolean result and an invalid event for a failing control. It does not display the browser’s validation message by itself; use reportValidity when you want the browser’s feedback. With native interactive validation enabled, invalid controls can block submission before React’s onSubmit handler runs. Handle invalid events or choose an explicit custom-validation flow instead of assuming onSubmit will always receive an invalid form.
The guide also covers the HTML novalidate attribute, written as noValidate in React. It disables automatic interactive validation during submission, but it does not remove the constraints or the constraint-validation API. You can still inspect validity and call checkValidity or reportValidity. If you use this approach, prevent invalid submissions deliberately and provide tested feedback.
Keep the input controlled, and know when it is not
React gives you two ways to hold a field's value. A controlled input takes its value from state and updates it on each change. An uncontrolled input lets the DOM hold the value, and you read it when you need it. React's reference for the input element lists the props that make an input controlled, including the requirement to pass an onChange handler that updates the value (React, input component).
Controlled inputs are useful when you need to react to every keystroke, such as showing a character count or formatting a phone number as the user types. For a form where you only need the values on submit, an uncontrolled input is often simpler. You can still read the element's validity at submit time without tracking every change in state.
Pick one approach per field and stay with it. A field that switches between controlled and uncontrolled causes confusing bugs, and React warns about it.
Submit with a form action, and read the data
React's form reference describes the action prop. When you pass a URL, the form behaves like a normal HTML form. When you pass a function, React calls it with the submitted form data as a single argument, and it can be async. The reference also notes that the action can be overridden by a formAction attribute on a button or submit input (React, form component).
Use normal form submission with native constraints when you want browser validation before the action. This is not a guarantee that all application rules have passed: noValidate, a submit control’s formNoValidate setting, or a different submission path can bypass interactive checks. MDN’s form.submit reference documents that direct submit() calls skip constraint validation and the submit event. Validate again on the server.
React’s form reference also states that uncontrolled fields reset after an action function succeeds. Handle validation responses deliberately so a failed application-level check does not unexpectedly erase the values the user needs to fix. Test the reset behavior of your chosen action pattern.
Show errors next to the field, in text
Show an error beside the affected field and explain what the user should change. An error summary can also help when several fields fail, especially when it links to the relevant controls. Choose when messages appear based on the task; avoid announcing an error on every keystroke while the user is still completing a value.
WCAG success criterion 3.3.1, Error Identification, has the intent that users are aware an error has occurred and can determine what is wrong (W3C WAI, Understanding SC 3.3.1). A red border alone does not tell a screen reader user what is wrong. Put the message in text. For custom feedback, associate its identifier with the input using aria-describedby and expose the field’s invalid state using aria-invalid when an error has been detected. Test the announcement in supported browser and screen reader combinations.
WCAG 3.3.3, Error Suggestion, has the intent that users receive appropriate suggestions for correcting an input error where possible (W3C WAI, Understanding SC 3.3.3). "Enter an email address, such as name@example.com" helps more than "Invalid input".
Keep the user's typed value when you show an error. Clearing a field after a failed check forces the user to type again, and the browser's built-in messages do not do that. If your React code resets the form on error, you have made the form harder to fix.
Label every field so the error makes sense
An error message only helps if the field has a clear label. WCAG 3.3.2, Labels or Instructions, has the intent that authors present labels or instructions that identify each control, so users know what input is expected (W3C WAI, Understanding SC 3.3.2). A placeholder is not a label. It disappears when the user types, and it does not reliably reach screen readers.
Announce errors that appear after a submit
When the errors appear after the user presses submit, a screen reader user needs to know they exist. WCAG 4.1.3, Status Messages, covers changes that do not take focus, and the criterion exists so assistive technology can announce them (W3C WAI, Understanding SC 4.1.3). If feedback appears without moving focus and qualifies as a status message, expose it through an appropriate live-region mechanism. Moving focus to the first invalid field is a separate strategy: associate the error with that field so the explanation is available when it receives focus. The W3C form notification guidance covers field associations, error summaries, and focus handling. Avoid duplicate announcements from combining both strategies indiscriminately.
Add React code only for rules the browser cannot express
Some rules need more than an attribute. A password must match a confirmation field. A discount code must be checked against a server. A start date must come before an end date. These are the cases where React logic earns its place. Write the client rule once, run it at an appropriate point, and show the result next to the field. To integrate a custom failure with native validity, use setCustomValidity with an explanatory message. MDN’s setCustomValidity reference explains that a nonempty message keeps the control invalid. Clear the message with an empty string after the rule passes; hiding the visible error alone does not clear custom validity.
Keep server checks on the server. A client check improves the experience, but it does not stop bad data from arriving. Treat the client check as the first pass, and handle the server response with the same field-level error display, so the user sees one consistent message.
Test the failure path
Test the happy path and the failure path. Submit an empty required form and check that focus moves to the first problem. Enter a bad email and check the message. Fix the email and check that the error clears. Use the keyboard only for the whole flow. Then turn on a screen reader and confirm that each message is read with its field.
Test with a slow network as well. If the server check takes a second, show that something is happening, and make sure the button cannot be pressed twice while the check runs.
Match the field to the data it holds
Choose input types for the meaning of the data. Email and URL types provide syntax checks, while a telephone input does not enforce a universal phone-number format. Use a text field for postal codes, which can include letters or leading zeros; there is no dedicated postcode input type. For a digits-only code, inputmode="numeric" can request a numeric keyboard without treating the value as a quantity. MDN’s inputmode reference explains that this is a keyboard hint, not a validation rule. Keyboard layouts vary by device.
Use the required attribute only on fields the form truly needs. Leave optional fields optional and explain which information is needed. MDN's guidance is to provide a visible indication near the control that the field is required (MDN, required attribute). A short "required" label beside the field does this, and the check the browser runs stays the same.
Move focus to the first problem
After a failed submit, the user should land on the first field that needs fixing, not at the top of the page. Find the first invalid element in the form order and give it focus. The browser's validity state tells you which element failed, so your code only needs to pick the first one and move focus there. Pair the focus move with the error text next to the field, so the user knows what to change as soon as they arrive.
Keep the order of the moves predictable. If the user fixes one field and submits again, focus should go to the next problem, not back to the first field each time. A consistent order makes the form feel calm, even when several fields need attention.
Clear the error when the field is fixed
An error message that stays on screen after the value is correct makes the form look broken. Re-check a field when the user changes it, and remove the error as soon as the value passes. Do the same check on blur if you want the message to appear when the user leaves a field, but keep the message quiet while the user is still typing. A message that flickers on each keystroke makes the form hard to use.
When the error clears, update the text the screen reader can reach as well. A message that disappears visually but stays in the accessibility tree confuses the next person who uses the form.
A short checklist
Native attributes handle the checks the browser already does.
Validity is read from the element, not duplicated in state.
Each input is either controlled or uncontrolled, consistently.
Errors appear next to their field, in text, and are linked to it.
Error messages suggest a fix where possible.
The user's typed value survives a failed check.
Server checks run on the server, and their errors show the same way.
The failure path works with the keyboard and a screen reader.
Let the browser do more than you think
A React form does not need to rebuild validation from nothing. The browser already knows how to check required fields and email addresses, and it reports the result on the element. Use those checks first, read the result from the element, and add React logic only where the rule is yours to define. Keep custom rules, accessible feedback, and server validation where they are needed. Test both a successful submission and a failed one before relying on the form’s behavior.