Skip to main content
Early access — new tools and guides added regularly
🟢 Zero to Claude Code — Guide 21 of 22
View track
>_ claude codeBeginner20 min

Building Forms and Handling User Input

Build accessible HTML forms with client-side validation, server-side processing, error handling, and a polished user experience.

What you will build
A multi-step form with validation, error messages, and server-side processing

HTML form fundamentals

Forms are how users send data to your application. Every login page, signup flow, checkout process, and search bar is a form. Understanding forms properly prevents security vulnerabilities, accessibility issues, and frustrating user experiences. A basic HTML form has a form element, input fields, labels, and a submit button. Ask Claude Code: Create a contact form page with Next.js. Include fields for name (text input), email (email input), subject (dropdown select), message (textarea), and a submit button. Use proper HTML semantics — every input must have a label element with a matching for attribute. Every input needs a name attribute (this is the key when the data is submitted), an id attribute (for the label to reference), a type attribute (text, email, password, number, tel, url, date), and optionally a placeholder (hint text shown when the field is empty). The type attribute matters more than you think. Type email shows an email keyboard on mobile and validates the format. Type tel shows a numeric keypad. Type password masks the input. Type number adds increment/decrement buttons. These are free usability improvements just from choosing the right type. Ask Claude Code: Review my form and ensure every input has the correct type, autocomplete attribute, and inputmode for optimal mobile experience. The autocomplete attribute tells the browser what kind of data the field expects: autocomplete="name", autocomplete="email", autocomplete="tel". This lets browsers auto-fill forms, which dramatically improves conversion rates — studies show auto-fill increases form completion by 25 to 30 percent.

Client-side validation

Validation checks that user input meets your requirements before it reaches the server. HTML provides built-in validation attributes: required (field must not be empty), minlength and maxlength (text length limits), min and max (number range limits), pattern (regex pattern matching), and type-specific validation (email must contain @, url must start with http). Add these to your form: the name input should be required with minlength 2 and maxlength 100. The email input should be required with type email. The message textarea should be required with minlength 10 and maxlength 2000. Ask Claude Code: Add HTML validation attributes to all my form fields with appropriate constraints. Include the pattern attribute for fields that need custom validation like phone numbers. When a user submits a form with invalid data, the browser shows default error messages. These are functional but ugly and inconsistent across browsers. For a polished experience, use JavaScript validation. Ask Claude Code: Create a custom form validation system using React state. For each field, track its value, whether it has been touched (the user clicked into and out of the field), and any error message. Show error messages below each field in red text when the field is touched and invalid. Hide errors until the user interacts with the field — showing all errors on page load is overwhelming. Use the onBlur event (fires when the user leaves a field) to trigger validation for individual fields. Use the onSubmit event to validate all fields at once. Disable the submit button until the form is valid to prevent frustrating error-submit-fix cycles. Common error: event.preventDefault() is essential in your submit handler. Without it, the browser submits the form traditionally (full page reload) instead of letting your JavaScript handle it.

Handling form submission and server actions

Client-side validation improves the user experience. Server-side validation is required for security. Never trust client-side validation alone — it can be bypassed by anyone with browser developer tools. Ask Claude Code: Create a Next.js server action to handle my contact form submission. Validate all fields on the server, sanitise the input to prevent XSS attacks, and return structured error messages for any validation failures. A server action in Next.js looks like: 'use server'; export async function submitContactForm(formData: FormData) { const name = formData.get('name') as string; // validate and process }. The server-side validation should mirror your client-side rules — same constraints, same error messages. If the server rejects the submission, return the errors in a format your form can display: { success: false, errors: { name: 'Name is required', email: 'Invalid email format' } }. Ask Claude Code: Update my form component to handle the server action response. Show server-side errors next to the relevant fields. Show a success message when the form is submitted successfully. Add loading state while the form is submitting — disable the submit button and show a spinner. For data processing after validation: sanitise strings to prevent stored XSS (use a library like DOMPurify or sanitize-html), trim whitespace from all fields, normalise email to lowercase, validate the email domain actually exists (optional but reduces fake submissions), and store the data in your database or send it to an email service. Common error: if your form works locally but fails in production, check that your server action is properly deployed and that environment variables (like email service API keys) are configured on your hosting platform.

Multi-step forms and complex inputs

Long forms have high abandonment rates. Breaking a form into steps reduces cognitive load and improves completion rates. Ask Claude Code: Convert my contact form into a multi-step wizard. Step 1: personal information (name, email, phone). Step 2: enquiry details (subject, message, priority). Step 3: review and submit (show a summary of all entered data). Include a progress indicator showing which step the user is on, back and next buttons, and validation that runs when the user clicks next (not before). Each step should validate independently — the user cannot proceed to step 2 until step 1 is valid. Store all form data in React state so it persists across steps. The review step shows everything the user entered and lets them go back to any step to make changes. Complex input types go beyond text fields. Ask Claude Code: Add these input types to my form: a file upload field (accept only PDF and images under 5MB), a date picker (restrict to future dates only), a combobox that searches and filters options as the user types, a group of checkboxes for selecting multiple interests, and a star rating component (1 to 5 stars, click or keyboard accessible). For file uploads, validate the file type and size on the client before uploading. Show a preview for images. Show the filename and size for documents. Use FormData to send files to the server — you cannot send files as JSON. Ask Claude Code: Create a file upload handler that validates the file on the server, stores it in a temporary location, and returns a URL. Handle errors like file too large, wrong type, and upload interrupted.

Accessibility and keyboard navigation

Forms are where accessibility matters most. A user who cannot tab through your form, who cannot hear error messages read by a screen reader, or who cannot see error states due to colour blindness cannot use your application. Every form field needs a visible label. Placeholder text is not a label — it disappears when the user starts typing, leaving them unable to remember what the field is for. Use the label element with htmlFor matching the input's id. Ask Claude Code: Audit my form for accessibility issues. Check that every input has a visible label, that error messages are associated with their fields using aria-describedby, that required fields are marked with aria-required, and that the form can be completed entirely with the keyboard. Keyboard navigation requirements: Tab moves to the next field. Shift+Tab moves to the previous field. Enter submits the form. Escape closes any dropdown. Arrow keys navigate within radio groups and select elements. The focus order should match the visual order. Test this by pressing Tab repeatedly through your form — the highlighted element should move logically from top to bottom. Error messages need aria-live="polite" so screen readers announce them when they appear. The region containing error messages should update dynamically without requiring the user to navigate to it. Ask Claude Code: Add ARIA attributes to my form for screen reader compatibility. Test with the browser's accessibility inspector and fix any issues. Colour is not sufficient for indicating errors. If your only error indicator is a red border, colour-blind users miss it. Add an error icon, error text below the field, and an aria-invalid attribute. Ask Claude Code: Ensure my form error states are perceivable without colour. Add icons and text descriptions alongside colour changes.

Form UX patterns and real-world polish

The difference between a form that converts and one that frustrates comes down to small details. Auto-save: for long forms, save progress to localStorage so users do not lose data if they close the tab. Ask Claude Code: Add auto-save to my multi-step form. Save all form data to localStorage on every change. When the page loads, restore any saved data. Clear saved data after successful submission. Show a "draft saved" indicator so users know their progress is safe. Inline validation: validate fields as the user types (with a debounce delay of 300 to 500 milliseconds) rather than waiting for blur or submit. For email fields, validate the format after the user stops typing. For password fields, show a strength indicator that updates in real-time. Ask Claude Code: Add inline validation with debouncing to my form. Show validation feedback as the user types, but only after they have entered at least 3 characters to avoid premature error messages. Smart defaults: pre-fill fields when you have context. If the user is logged in, pre-fill their name and email. If you know their country from the browser locale, pre-select it in the country dropdown. If the form is for a return visit, restore their last submission as defaults. Error recovery: when the server returns an error, do not clear the form. Keep all entered data and highlight only the fields that need correction. Show a clear summary of what went wrong and what the user needs to do. Ask Claude Code: Create a comprehensive error handling system for my form. Handle network errors (show a retry button), validation errors (highlight fields), rate limiting (show a countdown), and server errors (show a friendly message with a support contact link). Test the complete form by submitting with valid data, invalid data, no data, extremely long data, and special characters. Ask Claude Code: Generate test data sets for my form — valid submissions, edge cases, and attack vectors (XSS payloads, SQL injection strings). Verify the form handles all of them gracefully.

Related Lesson

Building User Interfaces

This guide is hands-on and practical. The full curriculum covers the conceptual foundations in depth with structured lessons and quizzes.

Go to lesson