A React form collects input through labeled controls, keeps those values in state, and submits them through a single handler. Building React forms comes down to three moves: label every control, track values in state, and stop the browser from reloading on submit. The signup form in this guide shows what appears as you type and what runs when you press Sign up.
Track a field value with state
Start with one controlled field. A controlled input takes its value from state and reports every change back through an onChange handler, so React state is the single source of truth for what the field shows.
import { useState } from "react";
export default function NameField() {
const [name, setName] = useState("");
return (
<label>
Full name
<input value={name} onChange={(e) => setName(e.target.value)} />
</label>
);
}The label wraps the input, so clicking the words Full name focuses the field. Each keystroke calls setName with the new text, and the input re-renders to show exactly what state holds.
Submit the form and prevent the page reload
Wrap your fields in a form element and point its onSubmit prop at a handler. The browser would normally reload the page with the form data, so the first line of the handler calls preventDefault to stop that default navigation.
import { useState } from "react";
export default function SignupForm() {
const [email, setEmail] = useState("");
function handleSubmit(e) {
e.preventDefault();
alert(`Signed up with ${email}`);
}
// the return below continues this same componentThe handler stops the default navigation, then reads the email straight from state. The return block below renders the form and the button that triggers it, still inside SignupForm.
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
<button type="submit">Sign up</button>
</form>
);
}Press the button and the form calls handleSubmit, which stops the reload and reads the current email from state. The button sits inside the form, so clicking it triggers the submit event for free. For a deeper look at why the reload happens and how to stop it, see how to prevent default form submission in React.
Read values with FormData when you do not track state
You do not have to store every value in state. An uncontrolled approach reads the fields at submit time through FormData, which collects every control by its name attribute. This is the pattern the React docs use for simple search and login forms.
export default function SearchForm() {
function handleSubmit(e) {
e.preventDefault();
const data = new FormData(e.target);
alert(`Searching for ${data.get("query")}`);
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="query">Search</label>
<input id="query" name="query" />
<button type="submit">Search</button>
</form>
);
}Here the input has no value or onChange prop, so the browser DOM owns the text. The handler reads it once from FormData when the form submits. The choice between these two approaches matters enough that controlled vs uncontrolled forms in React is its own guide.
Show a visible validation message
Before you accept a submission, check the value and show a message users can see and hear. Inside the SignupForm above, keep the message in an error state, mark the field with aria-invalid, and connect the message with aria-describedby.
<input
id="email"
type="email"
value={email}
aria-invalid={error ? "true" : "false"}
aria-describedby="email-error"
onChange={(e) => setEmail(e.target.value)}
/>
{error && <p id="email-error" role="alert">{error}</p>}aria-invalid flags the field for assistive technology, and aria-describedby ties the message to the input so screen readers announce it. The full check and display flow lives in React form validation without a library.
This flow is the same whether you use state or FormData. The handler always stops the reload first, then validation decides whether to accept the data or show the error. Server validation is the authority, not this client-side check.
Match the control to the data
The form element works with every native control, which makes React forms flexible. Choose the control that captures the data shape, and keep the same label plus state pattern for each one.
| Control | Use for |
|---|---|
| Text input | Short single-line values like a name |
| Email input | Addresses with a browser format check |
| Select | One choice from a fixed list |
| Textarea | Multi-line text like a message |
| Checkbox | A single yes or no value |
Checkboxes and radio groups read a checked value instead of text, and selects read value the same way text inputs do. The pattern stays the same: a semantic label, a value in state, and an onChange handler. React 19 also adds an action prop that runs a form submission in a transition and resets uncontrolled fields afterward, which is an alternative to the onSubmit handler used throughout this guide.
Rune AI
Key Insights
- Give every control a semantic label.
- Keep a field's value in state for a controlled input.
- Call preventDefault in the submit handler.
- Read uncontrolled values with FormData on submit.
- Validate on submit and show an accessible message.
Frequently Asked Questions
What is a controlled form input in React?
Why do I call preventDefault in a form submit handler?
Do I need a library to build forms in React?
Conclusion
A React form is labels, state, and a submit handler. Track values in state for live updates, or read them with FormData on submit, and always stop the default page reload.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.