React Form with Login Form
A login form looks simple but has more edge cases than most other form types: loading state during the auth request, an error state for invalid credentials, show/hide password toggle, and accessibility requirements for the error message. This example simulates an authentication attempt with a timeout and always shows an error — demonstrating the complete error handling pattern.
The challenge
Login forms require proper error state handling, a loading state during authentication, and accessibility considerations that go beyond a simple form.
- Showing a loading state on the button during the auth request so users do not double-submit
- Displaying the "invalid credentials" error without hinting which field is wrong (a security requirement)
- Making the error message accessible to screen readers using aria-live or role="alert"
- Resetting the error state when the user starts typing again after a failed attempt
Working code example
Here is a complete, self-contained working example you can drop directly into any React project. It uses Tailwind CSS for styling and requires no external dependencies beyond React itself.
import { useState } from 'react';
export default function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [showPwd, setShowPwd] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleChange = (setter) => (e) => {
setter(e.target.value);
if (error) setError('');
};
const handleSubmit = (e) => {
e.preventDefault();
if (!email.trim() || !password.trim()) {
setError('Please enter your email and password.');
return;
}
setLoading(true);
setError('');
setTimeout(() => {
setLoading(false);
setError('Invalid email or password. Please try again.');
}, 1500);
};
return (
<div className="max-w-sm mx-auto p-6 bg-white rounded-2xl shadow">
<h2 className="text-xl font-bold text-gray-800 mb-6">Sign in</h2>
{error && (
<div role="alert"
className="mb-4 bg-red-50 border border-red-200 text-red-700 text-sm rounded-xl px-4 py-3">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
Email address
</label>
<input
id="email"
type="email"
value={email}
onChange={handleChange(setEmail)}
autoComplete="email"
placeholder="you@example.com"
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-teal-500 transition"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
Password
</label>
<div className="relative">
<input
id="password"
type={showPwd ? 'text' : 'password'}
value={password}
onChange={handleChange(setPassword)}
autoComplete="current-password"
placeholder="••••••••"
className="w-full border border-gray-300 rounded-lg px-3 py-2 pr-16 text-sm outline-none focus:ring-2 focus:ring-teal-500 transition"
/>
<button type="button" onClick={() => setShowPwd((s) => !s)}
aria-label={showPwd ? 'Hide password' : 'Show password'}
className="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-teal-600 font-medium hover:underline">
{showPwd ? 'Hide' : 'Show'}
</button>
</div>
</div>
<button type="submit" disabled={loading}
className="w-full bg-teal-600 text-white font-semibold py-2.5 rounded-lg hover:bg-teal-700 transition disabled:opacity-60 disabled:cursor-not-allowed">
{loading ? 'Signing in…' : 'Sign in'}
</button>
</form>
<p className="mt-4 text-center text-sm text-gray-500">
Don't have an account?{' '}
<a href="#" className="text-teal-600 font-medium hover:underline">Sign up free</a>
</p>
</div>
);
}How ReactForm.co helps
ReactForm's visual builder handles all of the above — login form configuration, validation rules, state management, and responsive layout — without writing a single line of code. Drag fields onto the canvas, configure their properties in the sidebar, and get production-ready React output. You can publish the form and collect responses instantly, or export the JSX to drop into your own codebase.
Build this form visuallyFrequently asked questions
Should I tell the user which field is wrong in a login error message?
No. Saying "email not found" or "wrong password" leaks information about which accounts exist in your system. Always show a generic message like "Invalid email or password" that applies to both fields. This is a standard security practice for authentication forms.
How do I prevent duplicate form submissions during the loading state?
Set a loading boolean to true in handleSubmit before your async operation and set it back to false when done. Pass disabled={loading} to the submit button. This prevents the user from clicking submit again while the first request is in flight. Also disable any other interactive elements that could trigger a second submit.
How do I make login error messages accessible to screen readers?
Add role="alert" to the error container. This tells screen readers to announce the content as soon as it appears in the DOM, without the user needing to navigate to it. Alternatively, use aria-live="assertive" on a container that is always present in the DOM and update its text content when an error occurs.
Related topics
Build this form visually — no code needed
ReactForm.co handles login form fields, validation, conditional logic, and responsive layout automatically. Publish in minutes and collect responses for free.

