HTML forms are a fundamental part of web development, allowing users to input data that can be processed by websites and web applications. Forms are used for everything from simple contact forms to complex multi-step registration processes, login systems, search interfaces, and more.
Forms are the primary way users interact with websites and provide information. Well-designed forms improve user experience and increase conversion rates.
A basic HTML form consists of a <form>
element containing various form controls like text fields, checkboxes, radio buttons, and buttons.
< form action = "/submit-form" method = "post" >
< div >
< label for = "name" > Name: </ label >
< input type = "text" id = "name" name = "name" required >
</ div >
< div >
< label for = "email" > Email: </ label >
< input type = "email" id = "email" name = "email" required >
</ div >
< div >
< label for = "message" > Message: </ label >
< textarea id = "message" name = "message" rows = "4" required ></ textarea >
</ div >
< button type = "submit" > Submit </ button >
</ form >
Form Element
The <form>
element is the container for all form controls. It has attributes like action
(where to send the data) and method
(how to send it).
Form Controls
Elements like <input>
, <textarea>
, <select>
, and <button>
that collect user input.
Labels
The <label>
element associates text with form controls, improving usability and accessibility.
Submit Button
A button with type="submit"
that sends the form data to the server.
The <form>
element has several important attributes:
action Specifies where to send the form data when submitted. Can be a URL or a relative path.
method Specifies the HTTP method to use when sending form data (get or post).
enctype Specifies how form data should be encoded when submitted (important for file uploads).
autocomplete Specifies whether form controls should have autocomplete enabled.
novalidate When present, it specifies that the form should not be validated when submitted.
target Specifies where to display the response after submitting the form (_self, _blank, etc.).
<!-- Form that uploads files -->
< form action = "/upload" method = "post" enctype = "multipart/form-data" >
< input type = "file" name = "document" >
< button type = "submit" > Upload </ button >
</ form >
<!-- Form that opens response in a new tab -->
< form action = "/search" method = "get" target = "_blank" >
< input type = "search" name = "q" >
< button type = "submit" > Search </ button >
</ form >
The <input>
element is the most versatile form control. Its behavior changes dramatically based on its type
attribute.
Text Input Types
< label for = "name" > Name: </ label >
< input type = "text" id = "name" name = "name" placeholder = "Enter your name" >
< label for = "email" > Email: </ label >
< input type = "email" id = "email" name = "email" placeholder = "Enter your email" >
< label for = "password" > Password: </ label >
< input type = "password" id = "password" name = "password" >
< label for = "comment" > Comment: </ label >
< textarea id = "comment" name = "comment" rows = "4" cols = "50" ></ textarea >
text Basic single-line text input
email Input for email addresses with built-in validation
password Input that masks characters as they’re typed
textarea Multi-line text input
search Input for search queries
tel Input for telephone numbers
url Input for URLs with built-in validation
< label for = "age" > Age: </ label >
< input type = "number" id = "age" name = "age" min = "0" max = "120" >
< label for = "rating" > Rating: </ label >
< input type = "range" id = "rating" name = "rating" min = "1" max = "5" step = "1" >
< label for = "price" > Price: </ label >
< input type = "number" id = "price" name = "price" min = "0" step = "0.01" >
number Input for numerical values with optional min/max/step
range Slider control for selecting a value from a range
< label for = "birthdate" > Birth Date: </ label >
< input type = "date" id = "birthdate" name = "birthdate" >
< label for = "meeting-time" > Meeting Time: </ label >
< input type = "datetime-local" id = "meeting-time" name = "meeting-time" >
< label for = "appointment" > Appointment (date and time): </ label >
< input type = "datetime-local" id = "appointment" name = "appointment" >
< label for = "start-time" > Start Time: </ label >
< input type = "time" id = "start-time" name = "start-time" >
< label for = "vacation-week" > Vacation Week: </ label >
< input type = "week" id = "vacation-week" name = "vacation-week" >
< label for = "vacation-month" > Vacation Month: </ label >
< input type = "month" id = "vacation-month" name = "vacation-month" >
date Input for a date (year, month, day)
time Input for a time (hour, minute)
datetime-local Input for a date and time
month Input for a month and year
week Input for a week and year
< label for = "color" > Favorite Color: </ label >
< input type = "color" id = "color" name = "color" >
< fieldset >
< legend > Subscription: </ legend >
< div >
< input type = "radio" id = "free" name = "subscription" value = "free" checked >
< label for = "free" > Free </ label >
</ div >
< div >
< input type = "radio" id = "premium" name = "subscription" value = "premium" >
< label for = "premium" > Premium </ label >
</ div >
</ fieldset >
< fieldset >
< legend > Interests: </ legend >
< div >
< input type = "checkbox" id = "coding" name = "interests" value = "coding" >
< label for = "coding" > Coding </ label >
</ div >
< div >
< input type = "checkbox" id = "design" name = "interests" value = "design" >
< label for = "design" > Design </ label >
</ div >
< div >
< input type = "checkbox" id = "music" name = "interests" value = "music" >
< label for = "music" > Music </ label >
</ div >
</ fieldset >
< label for = "country" > Country: </ label >
< select id = "country" name = "country" >
< option value = "" > Select a country </ option >
< option value = "us" > United States </ option >
< option value = "ca" > Canada </ option >
< option value = "mx" > Mexico </ option >
</ select >
< label for = "languages" > Languages: </ label >
< select id = "languages" name = "languages" multiple >
< option value = "html" > HTML </ option >
< option value = "css" > CSS </ option >
< option value = "js" > JavaScript </ option >
</ select >
color Color picker control
radio Radio buttons for selecting one option from a group
checkbox Checkboxes for selecting multiple options
select Dropdown list for selecting one or multiple options
< label for = "profile-picture" > Profile Picture: </ label >
< input type = "file" id = "profile-picture" name = "profile-picture" accept = "image/*" >
< label for = "documents" > Documents: </ label >
< input type = "file" id = "documents" name = "documents" multiple accept = ".pdf,.doc,.docx" >
file Input for uploading files
accept Attribute to specify accepted file types
multiple Attribute to allow multiple file selection
< input type = "hidden" id = "user-id" name = "user-id" value = "12345" >
Hidden inputs are not displayed to the user but are included when the form is submitted. They’re useful for passing data that the user doesn’t need to see or modify.
Input elements have many attributes that control their behavior and appearance:
name Specifies the name of the input, which is submitted with the form data
value Specifies the initial value of the input
placeholder Provides a hint about what to enter in the input
required Specifies that the input must be filled out before submitting the form
disabled Disables the input, preventing user interaction
readonly Makes the input read-only, allowing it to be viewed but not modified
autofocus Automatically focuses the input when the page loads
autocomplete Controls browser autocomplete behavior for the input
min/max Specifies minimum and maximum values for numeric inputs
pattern Specifies a regular expression that the input’s value must match
step Specifies the interval between valid values for numeric inputs
maxlength Specifies the maximum number of characters allowed in the input
< input type = "text" name = "username" placeholder = "Username" required autofocus >
< input type = "email" name = "email" value = "user@example.com" readonly >
< input type = "number" name = "quantity" min = "1" max = "10" step = "1" value = "1" >
< input type = "text" name = "zipcode" pattern = "[0-9]{5}" placeholder = "5-digit ZIP code" >
< input type = "password" name = "password" minlength = "8" maxlength = "20" autocomplete = "new-password" >
Buttons are used to submit forms or trigger JavaScript actions:
<!-- Submit button -->
< button type = "submit" > Submit </ button >
<!-- Reset button -->
< button type = "reset" > Reset </ button >
<!-- Button for JavaScript actions -->
< button type = "button" onclick = " showAlert ()" > Click Me </ button >
<!-- Image button -->
< button type = "submit" >
< img src = "submit-icon.png" alt = "" > Submit
</ button >
<!-- Old-style submit button -->
< input type = "submit" value = "Submit" >
submit Submits the form data
reset Resets all form controls to their initial values
button Does nothing by default, typically used with JavaScript
The <button>
element is more flexible than <input type="submit">
because it can contain HTML content like images and formatted text.
Well-organized forms are easier to use and understand. Use these elements to structure your forms:
Fieldset and Legend
< form >
< fieldset >
< legend > Personal Information </ legend >
< div >
< label for = "first-name" > First Name: </ label >
< input type = "text" id = "first-name" name = "first-name" >
</ div >
< div >
< label for = "last-name" > Last Name: </ label >
< input type = "text" id = "last-name" name = "last-name" >
</ div >
</ fieldset >
< fieldset >
< legend > Contact Information </ legend >
< div >
< label for = "email" > Email: </ label >
< input type = "email" id = "email" name = "email" >
</ div >
< div >
< label for = "phone" > Phone: </ label >
< input type = "tel" id = "phone" name = "phone" >
</ div >
</ fieldset >
< button type = "submit" > Submit </ button >
</ form >
fieldset Groups related form controls together
legend Provides a caption for the fieldset
Datalist
The <datalist>
element provides a list of predefined options for an input element, creating an autocomplete feature:
< label for = "browser" > Choose your browser: </ label >
< input list = "browsers" id = "browser" name = "browser" >
< datalist id = "browsers" >
< option value = "Chrome" >
< option value = "Firefox" >
< option value = "Safari" >
< option value = "Edge" >
< option value = "Opera" >
</ datalist >
Unlike the <select>
element, <datalist>
allows users to enter values that aren’t in the list, providing both flexibility and suggestions.
Output
The <output>
element represents the result of a calculation or user action:
< form oninput = " result . value = parseInt ( a . value ) + parseInt ( b . value )" >
< input type = "number" id = "a" name = "a" value = "0" > +
< input type = "number" id = "b" name = "b" value = "0" > =
< output name = "result" for = "a b" > 0 </ output >
</ form >
Form validation ensures that user input is complete and in the correct format before submitting to the server.
Built-in Validation
HTML5 provides built-in validation for many input types:
< form >
< div >
< label for = "email" > Email: </ label >
< input type = "email" id = "email" name = "email" required >
<!-- Validates that the input is a properly formatted email address -->
</ div >
< div >
< label for = "website" > Website: </ label >
< input type = "url" id = "website" name = "website" >
<!-- Validates that the input is a properly formatted URL -->
</ div >
< div >
< label for = "age" > Age: </ label >
< input type = "number" id = "age" name = "age" min = "18" max = "120" >
<!-- Validates that the input is a number between 18 and 120 -->
</ div >
< div >
< label for = "phone" > Phone: </ label >
< input type = "tel" id = "phone" name = "phone" pattern = "[0-9]{3}-[0-9]{3}-[0-9]{4}" placeholder = "123-456-7890" >
<!-- Validates that the input matches the specified pattern -->
</ div >
< button type = "submit" > Submit </ button >
</ form >
While HTML5 validation is convenient, it’s not sufficient for security. Always validate form data on the server side as well.
Custom Validation with JavaScript
For more complex validation requirements, you can use JavaScript:
< form id = "registration-form" novalidate >
< div >
< label for = "password" > Password: </ label >
< input type = "password" id = "password" name = "password" required >
</ div >
< div >
< label for = "confirm-password" > Confirm Password: </ label >
< input type = "password" id = "confirm-password" name = "confirm-password" required >
< span id = "password-error" class = "error" ></ span >
</ div >
< button type = "submit" > Register </ button >
</ form >
< script >
const form = document . getElementById ( 'registration-form' );
const password = document . getElementById ( 'password' );
const confirmPassword = document . getElementById ( 'confirm-password' );
const passwordError = document . getElementById ( 'password-error' );
form . addEventListener ( 'submit' , function ( event ) {
// Check if passwords match
if ( password . value !== confirmPassword . value ) {
passwordError . textContent = 'Passwords do not match' ;
event . preventDefault (); // Prevent form submission
} else {
passwordError . textContent = '' ;
}
});
</ script >
The novalidate
attribute on the form disables the browser’s built-in validation, allowing you to implement custom validation with JavaScript.
Forms can be submitted in different ways:
< form action = "/submit" method = "post" >
<!-- Form fields -->
< button type = "submit" > Submit </ button >
</ form >
This causes the browser to navigate to the URL specified in the action
attribute, sending the form data using the HTTP method specified in the method
attribute.
Using JavaScript, you can submit forms asynchronously without page navigation:
< form id = "contact-form" >
< div >
< label for = "name" > Name: </ label >
< input type = "text" id = "name" name = "name" required >
</ div >
< div >
< label for = "email" > Email: </ label >
< input type = "email" id = "email" name = "email" required >
</ div >
< div >
< label for = "message" > Message: </ label >
< textarea id = "message" name = "message" required ></ textarea >
</ div >
< button type = "submit" > Send </ button >
< div id = "form-status" ></ div >
</ form >
< script >
const form = document . getElementById ( 'contact-form' );
const formStatus = document . getElementById ( 'form-status' );
form . addEventListener ( 'submit' , async function ( event ) {
event . preventDefault (); // Prevent traditional form submission
try {
const formData = new FormData ( form );
const response = await fetch ( '/api/contact' , {
method: 'POST' ,
body: formData
});
if ( response . ok ) {
formStatus . textContent = 'Message sent successfully!' ;
form . reset (); // Clear the form
} else {
formStatus . textContent = 'Error sending message. Please try again.' ;
}
} catch ( error ) {
formStatus . textContent = 'Network error. Please try again later.' ;
console . error ( 'Error:' , error );
}
});
</ script >
File Uploads
To handle file uploads, you need to set the form’s enctype
attribute to multipart/form-data
:
< form action = "/upload" method = "post" enctype = "multipart/form-data" >
< div >
< label for = "profile-picture" > Profile Picture: </ label >
< input type = "file" id = "profile-picture" name = "profile-picture" accept = "image/*" >
</ div >
< div >
< label for = "resume" > Resume: </ label >
< input type = "file" id = "resume" name = "resume" accept = ".pdf,.doc,.docx" >
</ div >
< button type = "submit" > Upload </ button >
</ form >
File uploads can pose security risks. Always validate file types and sizes on the server side, and scan uploaded files for malware if possible.
Forms often need custom styling to match your website’s design. Here’s a basic example of styled form elements:
< style >
.form-group {
margin-bottom : 1 rem ;
}
label {
display : block ;
margin-bottom : 0.5 rem ;
font-weight : bold ;
}
input [ type = "text" ],
input [ type = "email" ],
input [ type = "password" ],
textarea ,
select {
width : 100 % ;
padding : 0.5 rem ;
border : 1 px solid #ccc ;
border-radius : 4 px ;
font-size : 1 rem ;
}
input [ type = "text" ] :focus ,
input [ type = "email" ] :focus ,
input [ type = "password" ] :focus ,
textarea :focus ,
select :focus {
border-color : #0066ff ;
outline : none ;
box-shadow : 0 0 0 3 px rgba ( 0 , 102 , 255 , 0.25 );
}
.checkbox-group ,
.radio-group {
display : flex ;
align-items : center ;
margin-bottom : 0.5 rem ;
}
.checkbox-group input ,
.radio-group input {
margin-right : 0.5 rem ;
}
button {
padding : 0.5 rem 1 rem ;
background-color : #0066ff ;
color : white ;
border : none ;
border-radius : 4 px ;
font-size : 1 rem ;
cursor : pointer ;
}
button :hover {
background-color : #0052cc ;
}
.error-message {
color : #d9534f ;
font-size : 0.875 rem ;
margin-top : 0.25 rem ;
}
</ style >
< form >
< div class = "form-group" >
< label for = "name" > Name </ label >
< input type = "text" id = "name" name = "name" required >
</ div >
< div class = "form-group" >
< label for = "email" > Email </ label >
< input type = "email" id = "email" name = "email" required >
< div class = "error-message" ></ div >
</ div >
< div class = "form-group" >
< label > Subscription </ label >
< div class = "radio-group" >
< input type = "radio" id = "free" name = "subscription" value = "free" checked >
< label for = "free" > Free </ label >
</ div >
< div class = "radio-group" >
< input type = "radio" id = "premium" name = "subscription" value = "premium" >
< label for = "premium" > Premium </ label >
</ div >
</ div >
< div class = "form-group" >
< label > Interests </ label >
< div class = "checkbox-group" >
< input type = "checkbox" id = "coding" name = "interests" value = "coding" >
< label for = "coding" > Coding </ label >
</ div >
< div class = "checkbox-group" >
< input type = "checkbox" id = "design" name = "interests" value = "design" >
< label for = "design" > Design </ label >
</ div >
</ div >
< button type = "submit" > Submit </ button >
</ form >
Accessible forms ensure that all users, including those with disabilities, can interact with your forms:
Labels Always use <label>
elements properly associated with form controls
Fieldset & Legend Group related controls and provide a description
Error Messages Make error messages clear and associate them with the relevant fields
Focus Styles Ensure focus states are visible for keyboard navigation
ARIA Attributes Use ARIA attributes when necessary to improve accessibility
Tab Order Ensure a logical tab order for keyboard navigation
< form >
< div >
< label for = "username" > Username: </ label >
< input type = "text" id = "username" name = "username" aria-required = "true" aria-describedby = "username-help" >
< p id = "username-help" > Your username must be 5-20 characters long. </ p >
</ div >
< div >
< label for = "password" > Password: </ label >
< input type = "password" id = "password" name = "password" aria-required = "true" aria-describedby = "password-error" >
< p id = "password-error" role = "alert" class = "error" ></ p >
</ div >
< button type = "submit" > Log In </ button >
</ form >
The aria-describedby
attribute associates help text or error messages with form controls, ensuring screen readers announce this information when the control is focused.
Use appropriate input types to take advantage of built-in validation and appropriate mobile keyboards
Label all form controls clearly and associate labels with inputs using the for
attribute
Group related fields with <fieldset>
and <legend>
Provide clear error messages that explain how to fix the problem
Use placeholder text as hints , not as replacements for labels
Make forms keyboard accessible by ensuring all controls can be navigated and operated with a keyboard
Implement both client-side and server-side validation for security
Use autocomplete attributes appropriately to help users fill forms faster
Keep forms as short as possible by only asking for necessary information
Test forms on different devices and browsers to ensure compatibility
Never use the autofocus
attribute on forms that appear below other content, as it can disorient users, especially those using screen readers. Use it only when the form is the primary content of the page.
< form action = "/login" method = "post" >
< div >
< label for = "username" > Username: </ label >
< input type = "text" id = "username" name = "username" autocomplete = "username" required >
</ div >
< div >
< label for = "password" > Password: </ label >
< input type = "password" id = "password" name = "password" autocomplete = "current-password" required >
</ div >
< div >
< input type = "checkbox" id = "remember" name = "remember" >
< label for = "remember" > Remember me </ label >
</ div >
< button type = "submit" > Log In </ button >
< div >
< a href = "/forgot-password" > Forgot password? </ a >
</ div >
</ form >
< form action = "/search" method = "get" role = "search" >
< label for = "search" class = "visually-hidden" > Search: </ label >
< input type = "search" id = "search" name = "q" placeholder = "Search..." required >
< button type = "submit" aria-label = "Search" >
< svg aria-hidden = "true" focusable = "false" width = "16" height = "16" fill = "currentColor" viewBox = "0 0 16 16" >
<!-- Search icon SVG path -->
< path d = "M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z" />
</ svg >
</ button >
</ form >
Newsletter Signup
< form action = "/subscribe" method = "post" >
< div >
< label for = "email" > Subscribe to our newsletter: </ label >
< input type = "email" id = "email" name = "email" placeholder = "Your email address" required >
</ div >
< div >
< input type = "checkbox" id = "terms" name = "terms" required >
< label for = "terms" > I agree to the < a href = "/terms" > terms and conditions </ a ></ label >
</ div >
< button type = "submit" > Subscribe </ button >
</ form >
< form id = "multi-step-form" >
<!-- Step 1: Personal Information -->
< div class = "form-step" id = "step1" >
< h2 > Personal Information </ h2 >
< div >
< label for = "first-name" > First Name: </ label >
< input type = "text" id = "first-name" name = "first-name" required >
</ div >
< div >
< label for = "last-name" > Last Name: </ label >
< input type = "text" id = "last-name" name = "last-name" required >
</ div >
< button type = "button" onclick = " nextStep ( 1 , 2 )" > Next </ button >
</ div >
<!-- Step 2: Contact Information -->
< div class = "form-step" id = "step2" style = "display: none;" >
< h2 > Contact Information </ h2 >
< div >
< label for = "email" > Email: </ label >
< input type = "email" id = "email" name = "email" required >
</ div >
< div >
< label for = "phone" > Phone: </ label >
< input type = "tel" id = "phone" name = "phone" >
</ div >
< button type = "button" onclick = " nextStep ( 2 , 1 )" > Previous </ button >
< button type = "button" onclick = " nextStep ( 2 , 3 )" > Next </ button >
</ div >
<!-- Step 3: Review and Submit -->
< div class = "form-step" id = "step3" style = "display: none;" >
< h2 > Review and Submit </ h2 >
< div id = "form-summary" >
<!-- Will be filled with JavaScript -->
</ div >
< button type = "button" onclick = " nextStep ( 3 , 2 )" > Previous </ button >
< button type = "submit" > Submit </ button >
</ div >
</ form >
< script >
function nextStep ( currentStep , nextStep ) {
// Validate current step if moving forward
if ( nextStep > currentStep ) {
const currentStepElement = document . getElementById ( `step ${ currentStep } ` );
const inputs = currentStepElement . querySelectorAll ( 'input[required]' );
let isValid = true ;
inputs . forEach ( input => {
if ( ! input . value ) {
isValid = false ;
input . classList . add ( 'error' );
} else {
input . classList . remove ( 'error' );
}
});
if ( ! isValid ) return ;
}
// Hide current step and show next step
document . getElementById ( `step ${ currentStep } ` ). style . display = 'none' ;
document . getElementById ( `step ${ nextStep } ` ). style . display = 'block' ;
// If moving to review step, populate summary
if ( nextStep === 3 ) {
const formData = new FormData ( document . getElementById ( 'multi-step-form' ));
let summary = '<dl>' ;
for ( const [ key , value ] of formData . entries ()) {
if ( value ) {
const label = document . querySelector ( `label[for=" ${ key } "]` )?. textContent || key ;
summary += `<dt> ${ label } </dt><dd> ${ value } </dd>` ;
}
}
summary += '</dl>' ;
document . getElementById ( 'form-summary' ). innerHTML = summary ;
}
}
document . getElementById ( 'multi-step-form' ). addEventListener ( 'submit' , function ( event ) {
event . preventDefault ();
// Submit form data via AJAX or other method
alert ( 'Form submitted successfully!' );
});
</ script >
Conclusion
HTML forms are a crucial part of web development, enabling user interaction and data collection. By understanding the various form elements, attributes, and best practices, you can create forms that are user-friendly, accessible, and secure.
Remember that forms should be designed with the user in mind, making them as simple and intuitive as possible while still collecting the necessary information. Always validate form data on both the client and server sides, and ensure your forms are accessible to all users, including those with disabilities.