Table of Contents
- Introduction to Forms
- Form Structure and Elements
- Input Types
- Form Validation
- Styling Forms with CSS
- Advanced Form Components
- Form Layouts
- Form Validation Techniques
- Custom Form Elements
- Accessibility in Forms
- Form Security
- Practical Examples
- Best Practices
Introduction to Forms
Forms are the primary way users interact with websites - they're used for login, registration, search, feedback, purchases, and much more. A well-designed form can significantly improve user experience and conversion rates.
Why Forms Matter
- User Interaction: Primary method for user input
- Data Collection: Gather information from users
- Authentication: Login and registration
- E-commerce: Checkout and payment processing
- Communication: Contact forms and feedback
- Search: Query submission
Form Structure and Elements
Basic Form Structure
<!-- Basic form structure --> <form action="/submit-form" method="post" enctype="multipart/form-data"> <!-- Form fields go here --> <button type="submit">Submit</button> </form> <!-- Form attributes explained --> <form action="/api/users" <!-- Where to send the data --> method="post" <!-- HTTP method: get, post --> enctype="multipart/form-data" <!-- Encoding for file uploads --> name="registration" <!-- Form name --> id="register-form" <!-- Form ID --> class="form" <!-- CSS class --> autocomplete="on" <!-- Enable/disable autocomplete --> novalidate <!-- Disable browser validation --> target="_blank" <!-- Where to display response --> >
Fieldset and Legend
<!-- Grouping related fields --> <fieldset> <legend>Personal Information</legend> <div class="form-group"> <label for="first-name">First Name:</label> <input type="text" id="first-name" name="first_name"> </div> <div class="form-group"> <label for="last-name">Last Name:</label> <input type="text" id="last-name" name="last_name"> </div> </fieldset> <fieldset> <legend>Account Information</legend> <div class="form-group"> <label for="username">Username:</label> <input type="text" id="username" name="username"> </div> <div class="form-group"> <label for="password">Password:</label> <input type="password" id="password" name="password"> </div> </fieldset>
Labels and Input Association
<!-- Method 1: Using for and id attributes --> <label for="email">Email Address:</label> <input type="email" id="email" name="email"> <!-- Method 2: Wrapping input with label --> <label> Email Address: <input type="email" name="email"> </label> <!-- Method 3: Multiple labels (avoid) --> <label for="phone">Phone:</label> <label for="phone">(optional)</label> <input type="tel" id="phone" name="phone">
Input Types
Text Inputs
<!-- Basic text input --> <input type="text" name="username" placeholder="Enter username"> <!-- Password input --> <input type="password" name="password" placeholder="********"> <!-- Email input with validation --> <input type="email" name="email" placeholder="[email protected]" required> <!-- Telephone input --> <input type="tel" name="phone" placeholder="123-456-7890" pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"> <!-- URL input --> <input type="url" name="website" placeholder="https://example.com"> <!-- Search input --> <input type="search" name="q" placeholder="Search..."> <!-- Textarea (multi-line) --> <textarea name="message" rows="5" cols="40" placeholder="Enter your message"></textarea>
Number Inputs
<!-- Basic number input --> <input type="number" name="age" min="0" max="150" step="1" value="18"> <!-- Range slider --> <input type="range" name="volume" min="0" max="100" value="50" step="1"> <!-- With datalist (suggestions) --> <input type="text" name="country" list="countries"> <datalist id="countries"> <option value="United States"> <option value="Canada"> <option value="Mexico"> <option value="United Kingdom"> </datalist>
Date and Time Inputs
<!-- Date picker --> <input type="date" name="birthday" min="1900-01-01" max="2024-12-31"> <!-- Time picker --> <input type="time" name="appointment" min="09:00" max="17:00"> <!-- DateTime local --> <input type="datetime-local" name="meeting"> <!-- Month picker --> <input type="month" name="billing-month"> <!-- Week picker --> <input type="week" name="week-number">
Selection Inputs
<!-- Checkbox --> <label> <input type="checkbox" name="subscribe" value="newsletter" checked> Subscribe to newsletter </label> <!-- Multiple checkboxes --> <fieldset> <legend>Interests</legend> <label> <input type="checkbox" name="interests[]" value="tech"> Technology </label> <label> <input type="checkbox" name="interests[]" value="sports"> Sports </label> <label> <input type="checkbox" name="interests[]" value="music"> Music </label> </fieldset> <!-- Radio buttons (mutually exclusive) --> <fieldset> <legend>Gender</legend> <label> <input type="radio" name="gender" value="male" checked> Male </label> <label> <input type="radio" name="gender" value="female"> Female </label> <label> <input type="radio" name="gender" value="other"> Other </label> </fieldset> <!-- Select dropdown --> <label for="country">Country:</label> <select id="country" name="country" required> <option value="">-- Select your country --</option> <option value="us">United States</option> <option value="ca">Canada</option> <option value="uk">United Kingdom</option> <option value="au">Australia</option> </select> <!-- Select with optgroups --> <label for="car">Car model:</label> <select id="car" name="car"> <optgroup label="American"> <option value="ford">Ford</option> <option value="chevrolet">Chevrolet</option> <option value="tesla">Tesla</option> </optgroup> <optgroup label="European"> <option value="bmw">BMW</option> <option value="audi">Audi</option> <option value="mercedes">Mercedes</option> </optgroup> <optgroup label="Japanese"> <option value="toyota">Toyota</option> <option value="honda">Honda</option> <option value="nissan">Nissan</option> </optgroup> </select> <!-- Multiple select --> <label for="colors">Favorite colors (hold Ctrl to select multiple):</label> <select id="colors" name="colors[]" multiple size="4"> <option value="red">Red</option> <option value="green">Green</option> <option value="blue">Blue</option> <option value="yellow">Yellow</option> <option value="purple">Purple</option> <option value="orange">Orange</option> </select>
File Inputs
<!-- Basic file upload --> <input type="file" name="document"> <!-- File upload with accept attribute --> <input type="file" name="profile-pic" accept="image/*"> <input type="file" name="resume" accept=".pdf,.doc,.docx"> <input type="file" name="photo" accept="image/png, image/jpeg"> <!-- Multiple file upload --> <input type="file" name="gallery" multiple accept="image/*"> <!-- File upload with capture (mobile) --> <input type="file" name="selfie" accept="image/*" capture="user"> <input type="file" name="document" accept="image/*" capture="environment">
Hidden and Button Inputs
<!-- Hidden input (for CSRF tokens, IDs, etc.) -->
<input type="hidden" name="user_id" value="12345">
<input type="hidden" name="csrf_token" value="abc123def456">
<!-- Submit button -->
<input type="submit" value="Save Changes">
<!-- Reset button (use carefully!) -->
<input type="reset" value="Clear Form">
<!-- Generic button -->
<input type="button" value="Click Me" onclick="alert('Clicked!')">
<!-- Image as button -->
<input type="image" src="submit-button.png" alt="Submit" width="100">
HTML5 Input Attributes
<!-- Placeholder text -->
<input type="text" placeholder="Enter your name">
<!-- Required field -->
<input type="email" required>
<!-- Disabled field -->
<input type="text" value="Cannot edit" disabled>
<!-- Readonly field -->
<input type="text" value="Can see but not edit" readonly>
<!-- Autofocus on page load -->
<input type="text" autofocus>
<!-- Autocomplete -->
<input type="text" name="full-name" autocomplete="name">
<input type="email" name="email" autocomplete="email">
<input type="tel" name="phone" autocomplete="tel">
<!-- Pattern validation -->
<input type="text" pattern="[A-Za-z]{3,}" title="At least 3 letters">
<!-- Min/max length -->
<input type="text" minlength="3" maxlength="20">
<!-- Step attribute -->
<input type="number" step="5" min="0" max="100">
<!-- Multiple values (email) -->
<input type="email" multiple placeholder="Enter emails separated by commas">
<!-- Spellcheck -->
<textarea spellcheck="true"></textarea>
<!-- Wrap attribute for textarea -->
<textarea wrap="soft"></textarea> <!-- Default -->
<textarea wrap="hard"></textarea> <!-- Preserves line breaks -->
<!-- Input mode for mobile keyboards -->
<input type="text" inputmode="numeric"> <!-- Shows numeric keyboard -->
<input type="text" inputmode="email"> <!-- Shows email keyboard -->
<input type="text" inputmode="tel"> <!-- Shows telephone keyboard -->
<input type="text" inputmode="url"> <!-- Shows URL keyboard -->
<input type="text" inputmode="search"> <!-- Shows search keyboard -->
Form Validation
HTML5 Built-in Validation
<!-- Required fields -->
<input type="text" required>
<!-- Email validation -->
<input type="email" required>
<!-- URL validation -->
<input type="url" required>
<!-- Number range -->
<input type="number" min="18" max="99" required>
<!-- Pattern validation -->
<input type="text" pattern="[A-Za-z0-9]{5,10}"
title="5-10 alphanumeric characters">
<!-- Length validation -->
<input type="text" minlength="3" maxlength="20" required>
<!-- Step validation -->
<input type="number" step="5" min="0" max="100">
<!-- Custom validation with JavaScript -->
<script>
function validateForm() {
const password = document.getElementById('password').value;
const confirm = document.getElementById('confirm-password').value;
if (password !== confirm) {
alert('Passwords do not match!');
return false;
}
return true;
}
</script>
<form onsubmit="return validateForm()">
<input type="password" id="password" required>
<input type="password" id="confirm-password" required>
<button type="submit">Register</button>
</form>
Constraint Validation API
<form id="registration-form">
<div class="form-group">
<label for="username">Username:</label>
<input type="text"
id="username"
name="username"
required
minlength="3"
maxlength="20"
pattern="[A-Za-z0-9_]+"
title="Only letters, numbers, and underscores">
<span class="error-message" id="username-error"></span>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
<span class="error-message" id="email-error"></span>
</div>
<div class="form-group">
<label for="age">Age:</label>
<input type="number" id="age" name="age" min="18" max="120" required>
<span class="error-message" id="age-error"></span>
</div>
<button type="submit">Register</button>
</form>
<script>
document.getElementById('registration-form').addEventListener('submit', function(e) {
e.preventDefault();
const username = document.getElementById('username');
const email = document.getElementById('email');
const age = document.getElementById('age');
let isValid = true;
// Username validation
if (!username.validity.valid) {
document.getElementById('username-error').textContent =
username.validationMessage ||
'Username must be 3-20 alphanumeric characters';
isValid = false;
} else {
document.getElementById('username-error').textContent = '';
}
// Email validation
if (!email.validity.valid) {
document.getElementById('email-error').textContent =
email.validationMessage || 'Please enter a valid email';
isValid = false;
} else {
document.getElementById('email-error').textContent = '';
}
// Age validation
if (!age.validity.valid) {
document.getElementById('age-error').textContent =
age.validationMessage || 'You must be at least 18 years old';
isValid = false;
} else {
document.getElementById('age-error').textContent = '';
}
if (isValid) {
// Submit form
this.submit();
}
});
// Real-time validation
document.getElementById('username').addEventListener('input', function() {
if (this.validity.valid) {
this.classList.remove('invalid');
this.classList.add('valid');
document.getElementById('username-error').textContent = '';
} else {
this.classList.remove('valid');
this.classList.add('invalid');
}
});
</script>
Custom Validation Messages
<form id="custom-validation-form">
<div class="form-group">
<label for="password">Password:</label>
<input type="password"
id="password"
name="password"
required
minlength="8"
pattern="^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$"
title="At least 8 characters, one letter and one number">
<span class="error-message" id="password-error"></span>
</div>
<div class="form-group">
<label for="confirm-password">Confirm Password:</label>
<input type="password" id="confirm-password" name="confirm-password" required>
<span class="error-message" id="confirm-error"></span>
</div>
<button type="submit">Change Password</button>
</form>
<script>
document.getElementById('custom-validation-form').addEventListener('submit', function(e) {
e.preventDefault();
const password = document.getElementById('password');
const confirm = document.getElementById('confirm-password');
// Check password strength
const hasLetter = /[A-Za-z]/.test(password.value);
const hasNumber = /\d/.test(password.value);
const hasMinLength = password.value.length >= 8;
if (!hasLetter || !hasNumber || !hasMinLength) {
password.setCustomValidity('Password must be at least 8 characters with at least one letter and one number');
} else {
password.setCustomValidity('');
}
// Check password match
if (password.value !== confirm.value) {
confirm.setCustomValidity('Passwords do not match');
} else {
confirm.setCustomValidity('');
}
if (password.checkValidity() && confirm.checkValidity()) {
alert('Form is valid!');
// this.submit();
} else {
password.reportValidity();
confirm.reportValidity();
}
});
</script>
Styling Forms with CSS
Basic Form Styling
/* Form container */
.form-container {
max-width: 500px;
margin: 2rem auto;
padding: 2rem;
background: #f9f9f9;
border-radius: 10px;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
}
/* Form groups */
.form-group {
margin-bottom: 1.5rem;
}
/* Labels */
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #333;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Required field indicator */
label.required::after {
content: ' *';
color: #ff4444;
font-weight: normal;
}
/* Input styling */
input, select, textarea {
width: 100%;
padding: 0.75rem 1rem;
border: 2px solid #e0e0e0;
border-radius: 6px;
font-size: 1rem;
font-family: inherit;
transition: all 0.3s ease;
background: white;
}
/* Focus state */
input:focus, select:focus, textarea:focus {
outline: none;
border-color: #4CAF50;
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
}
/* Hover state */
input:hover, select:hover, textarea:hover {
border-color: #b0b0b0;
}
/* Disabled state */
input:disabled, select:disabled, textarea:disabled {
background: #f5f5f5;
cursor: not-allowed;
opacity: 0.7;
}
/* Readonly state */
input:readonly, textarea:readonly {
background: #f9f9f9;
border-color: #ddd;
cursor: default;
}
/* Placeholder styling */
::placeholder {
color: #999;
opacity: 1;
}
/* Validation states */
input.valid, select.valid, textarea.valid {
border-color: #4CAF50;
}
input.invalid, select.invalid, textarea.invalid {
border-color: #ff4444;
}
/* Error messages */
.error-message {
color: #ff4444;
font-size: 0.85rem;
margin-top: 0.25rem;
display: block;
}
/* Success messages */
.success-message {
color: #4CAF50;
font-size: 0.85rem;
margin-top: 0.25rem;
display: block;
}
Button Styling
/* Base button styles */
button, .button {
display: inline-block;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 6px;
font-size: 1rem;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition: all 0.3s ease;
}
/* Primary button */
.btn-primary {
background: #4CAF50;
color: white;
}
.btn-primary:hover {
background: #45a049;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.3);
}
.btn-primary:active {
transform: translateY(0);
}
/* Secondary button */
.btn-secondary {
background: #f0f0f0;
color: #333;
}
.btn-secondary:hover {
background: #e0e0e0;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
/* Danger button */
.btn-danger {
background: #ff4444;
color: white;
}
.btn-danger:hover {
background: #ff3333;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(255, 68, 68, 0.3);
}
/* Outline button */
.btn-outline {
background: transparent;
border: 2px solid #4CAF50;
color: #4CAF50;
}
.btn-outline:hover {
background: #4CAF50;
color: white;
}
/* Disabled button */
button:disabled, .button:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
/* Full width button */
.btn-block {
display: block;
width: 100%;
}
/* Button sizes */
.btn-small {
padding: 0.4rem 0.8rem;
font-size: 0.875rem;
}
.btn-large {
padding: 1rem 2rem;
font-size: 1.125rem;
}
/* Button group */
.button-group {
display: flex;
gap: 10px;
}
.button-group .btn {
flex: 1;
}
Custom Select Styling
/* Custom select wrapper */
.custom-select {
position: relative;
width: 100%;
}
/* Hide default arrow */
.custom-select select {
appearance: none;
-webkit-appearance: none;
width: 100%;
padding: 0.75rem 1rem;
background: white;
border: 2px solid #e0e0e0;
border-radius: 6px;
cursor: pointer;
}
/* Custom arrow */
.custom-select::after {
content: 'βΌ';
position: absolute;
top: 50%;
right: 1rem;
transform: translateY(-50%);
pointer-events: none;
color: #666;
font-size: 0.8rem;
}
/* Multiple select */
select[multiple] {
padding: 0;
height: auto;
}
select[multiple] option {
padding: 0.5rem 1rem;
}
select[multiple] option:checked {
background: #4CAF50 linear-gradient(0deg, #4CAF50 0%, #4CAF50 100%);
color: white;
}
Checkbox and Radio Styling
/* Hide default checkbox */
.custom-checkbox {
position: relative;
display: inline-block;
padding-left: 30px;
margin-right: 15px;
cursor: pointer;
user-select: none;
}
.custom-checkbox input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
/* Custom checkbox */
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 20px;
width: 20px;
background-color: #fff;
border: 2px solid #e0e0e0;
border-radius: 4px;
transition: all 0.3s;
}
/* Hover state */
.custom-checkbox:hover input ~ .checkmark {
border-color: #4CAF50;
}
/* Checked state */
.custom-checkbox input:checked ~ .checkmark {
background-color: #4CAF50;
border-color: #4CAF50;
}
/* Checkmark icon */
.checkmark:after {
content: "";
position: absolute;
display: none;
}
.custom-checkbox input:checked ~ .checkmark:after {
display: block;
}
.custom-checkbox .checkmark:after {
left: 6px;
top: 2px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
/* Focus state */
.custom-checkbox input:focus ~ .checkmark {
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.3);
}
/* Disabled state */
.custom-checkbox input:disabled ~ .checkmark {
opacity: 0.5;
cursor: not-allowed;
}
/* Radio button styling */
.custom-radio {
position: relative;
display: inline-block;
padding-left: 30px;
margin-right: 15px;
cursor: pointer;
user-select: none;
}
.custom-radio input {
position: absolute;
opacity: 0;
}
.radio-mark {
position: absolute;
top: 0;
left: 0;
height: 20px;
width: 20px;
background-color: #fff;
border: 2px solid #e0e0e0;
border-radius: 50%;
transition: all 0.3s;
}
.custom-radio:hover input ~ .radio-mark {
border-color: #4CAF50;
}
.custom-radio input:checked ~ .radio-mark {
border-color: #4CAF50;
background-color: #fff;
}
.radio-mark:after {
content: "";
position: absolute;
display: none;
top: 4px;
left: 4px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #4CAF50;
}
.custom-radio input:checked ~ .radio-mark:after {
display: block;
}
/* Inline checkboxes/radios */
.checkbox-group, .radio-group {
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: center;
}
Toggle Switch (Checkbox)
/* Toggle switch */
.toggle-switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .toggle-slider {
background-color: #4CAF50;
}
input:focus + .toggle-slider {
box-shadow: 0 0 1px #4CAF50;
}
input:checked + .toggle-slider:before {
transform: translateX(26px);
}
/* Disabled state */
input:disabled + .toggle-slider {
opacity: 0.5;
cursor: not-allowed;
}
File Input Styling
/* File input styling */
.file-input-wrapper {
position: relative;
display: inline-block;
width: 100%;
}
.file-input-wrapper input[type="file"] {
position: absolute;
top: 0;
left: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
z-index: 2;
}
.file-input-button {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 1rem;
background: #f0f0f0;
border: 2px dashed #ccc;
border-radius: 6px;
color: #666;
font-size: 1rem;
transition: all 0.3s;
}
.file-input-wrapper:hover .file-input-button {
border-color: #4CAF50;
color: #4CAF50;
background: #f5f5f5;
}
.file-name {
margin-top: 0.5rem;
font-size: 0.875rem;
color: #666;
}
/* Multiple file upload preview */
.file-preview {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}
.file-preview-item {
position: relative;
width: 80px;
height: 80px;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}
.file-preview-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
.file-preview-item .remove-file {
position: absolute;
top: 2px;
right: 2px;
width: 20px;
height: 20px;
background: rgba(255, 68, 68, 0.8);
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
Form Validation Styling
/* Validation styles */
input:valid {
border-color: #4CAF50;
}
input:invalid:not(:focus):not(:placeholder-shown) {
border-color: #ff4444;
}
/* Validation icons */
.input-wrapper {
position: relative;
}
.input-wrapper::after {
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-50%);
font-size: 1rem;
}
.input-wrapper input:valid + .validation-icon::before {
content: 'β';
color: #4CAF50;
}
.input-wrapper input:invalid:not(:focus):not(:placeholder-shown) + .validation-icon::before {
content: 'β';
color: #ff4444;
}
/* Progress bar for password strength */
.password-strength {
height: 4px;
margin-top: 5px;
background: #e0e0e0;
border-radius: 2px;
overflow: hidden;
}
.password-strength-bar {
height: 100%;
width: 0;
transition: width 0.3s, background 0.3s;
}
.password-strength-bar.weak {
width: 33.33%;
background: #ff4444;
}
.password-strength-bar.medium {
width: 66.66%;
background: #ffbb33;
}
.password-strength-bar.strong {
width: 100%;
background: #4CAF50;
}
/* Password requirements list */
.password-requirements {
list-style: none;
padding: 0;
margin: 10px 0 0;
font-size: 0.85rem;
}
.password-requirements li {
margin-bottom: 5px;
color: #999;
}
.password-requirements li.valid {
color: #4CAF50;
}
.password-requirements li.valid::before {
content: 'β ';
}
.password-requirements li.invalid::before {
content: 'β ';
}
Advanced Form Components
Autocomplete and Datalist
<!-- Simple datalist --> <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> <!-- Datalist with labels --> <label for="country">Country:</label> <input list="countries" id="country" name="country"> <datalist id="countries"> <option value="US" label="United States"> <option value="CA" label="Canada"> <option value="UK" label="United Kingdom"> <option value="AU" label="Australia"> </datalist>
Input Groups
<!-- Input groups with addons --> <div class="input-group"> <span class="input-group-text">@</span> <input type="text" placeholder="Username"> </div> <div class="input-group"> <span class="input-group-text">https://</span> <input type="url" placeholder="example.com"> <span class="input-group-text">.com</span> </div> <div class="input-group"> <span class="input-group-text">$</span> <input type="number" placeholder="Amount" min="0" step="0.01"> <span class="input-group-text">.00</span> </div> <div class="input-group"> <input type="text" placeholder="Search"> <button class="input-group-button">π</button> </div>
/* Input group styling */
.input-group {
display: flex;
align-items: stretch;
width: 100%;
}
.input-group-text {
display: flex;
align-items: center;
padding: 0.75rem 1rem;
background: #f0f0f0;
border: 2px solid #e0e0e0;
color: #666;
font-size: 1rem;
}
.input-group-text:first-child {
border-right: none;
border-radius: 6px 0 0 6px;
}
.input-group-text:last-child {
border-left: none;
border-radius: 0 6px 6px 0;
}
.input-group input {
flex: 1;
min-width: 0;
border-radius: 0;
}
.input-group input:first-child {
border-radius: 6px 0 0 6px;
}
.input-group input:last-child {
border-radius: 0 6px 6px 0;
}
.input-group-button {
padding: 0 1rem;
background: #4CAF50;
color: white;
border: 2px solid #4CAF50;
border-radius: 0 6px 6px 0;
cursor: pointer;
transition: all 0.3s;
}
.input-group-button:hover {
background: #45a049;
}
Floating Labels
<div class="floating-label-group"> <input type="text" id="floating-name" placeholder=" "> <label for="floating-name">Full Name</label> </div> <div class="floating-label-group"> <input type="email" id="floating-email" placeholder=" "> <label for="floating-email">Email Address</label> </div> <div class="floating-label-group"> <textarea id="floating-message" placeholder=" "></textarea> <label for="floating-message">Message</label> </div>
/* Floating labels */
.floating-label-group {
position: relative;
margin-bottom: 1.5rem;
}
.floating-label-group input,
.floating-label-group textarea {
width: 100%;
padding: 1rem 0.75rem 0.5rem;
border: 2px solid #e0e0e0;
border-radius: 6px;
font-size: 1rem;
transition: all 0.3s;
}
.floating-label-group label {
position: absolute;
top: 0.75rem;
left: 0.75rem;
padding: 0 0.25rem;
background: white;
color: #999;
font-size: 1rem;
transition: all 0.3s;
pointer-events: none;
}
.floating-label-group input:focus,
.floating-label-group textarea:focus,
.floating-label-group input:not(:placeholder-shown),
.floating-label-group textarea:not(:placeholder-shown) {
padding-top: 1.25rem;
padding-bottom: 0.25rem;
}
.floating-label-group input:focus + label,
.floating-label-group textarea:focus + label,
.floating-label-group input:not(:placeholder-shown) + label,
.floating-label-group textarea:not(:placeholder-shown) + label {
top: -0.5rem;
left: 0.5rem;
font-size: 0.8rem;
color: #4CAF50;
}
.floating-label-group input:focus,
.floating-label-group textarea:focus {
border-color: #4CAF50;
}
Character Counter
<div class="character-counter-group">
<label for="bio">Bio (max 200 characters):</label>
<textarea id="bio" name="bio" maxlength="200" rows="4"></textarea>
<div class="character-counter">
<span id="char-count">0</span>/200 characters
</div>
</div>
<script>
document.getElementById('bio').addEventListener('input', function() {
const count = this.value.length;
const counter = document.getElementById('char-count');
counter.textContent = count;
if (count > 180) {
counter.style.color = '#ff4444';
} else if (count > 150) {
counter.style.color = '#ffbb33';
} else {
counter.style.color = '#666';
}
});
</script>
Password Strength Meter
<div class="password-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password">
<div class="password-strength">
<div class="password-strength-bar" id="strength-bar"></div>
</div>
<ul class="password-requirements" id="password-requirements">
<li id="req-length">At least 8 characters</li>
<li id="req-lower">At least one lowercase letter</li>
<li id="req-upper">At least one uppercase letter</li>
<li id="req-number">At least one number</li>
<li id="req-special">At least one special character</li>
</ul>
</div>
<script>
document.getElementById('password').addEventListener('input', function() {
const password = this.value;
const bar = document.getElementById('strength-bar');
const reqs = {
length: password.length >= 8,
lower: /[a-z]/.test(password),
upper: /[A-Z]/.test(password),
number: /\d/.test(password),
special: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)
};
// Update requirement indicators
for (const [req, met] of Object.entries(reqs)) {
const element = document.getElementById(`req-${req}`);
if (met) {
element.classList.add('valid');
element.classList.remove('invalid');
} else {
element.classList.add('invalid');
element.classList.remove('valid');
}
}
// Calculate strength
const metCount = Object.values(reqs).filter(Boolean).length;
let strength = 0;
let color = '';
if (metCount <= 2) {
strength = 33.33;
color = '#ff4444';
bar.className = 'password-strength-bar weak';
} else if (metCount <= 4) {
strength = 66.66;
color = '#ffbb33';
bar.className = 'password-strength-bar medium';
} else {
strength = 100;
color = '#4CAF50';
bar.className = 'password-strength-bar strong';
}
bar.style.width = strength + '%';
bar.style.background = color;
});
</script>
Form Layouts
Single Column Layout
<div class="form-container"> <h2>Contact Us</h2> <form class="form-single-column"> <div class="form-group"> <label for="name">Full 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> <div class="form-group"> <label for="subject">Subject</label> <input type="text" id="subject" name="subject"> </div> <div class="form-group"> <label for="message">Message *</label> <textarea id="message" name="message" rows="5" required></textarea> </div> <button type="submit" class="btn btn-primary btn-block">Send Message</button> </form> </div>
Two Column Layout
<form class="form-two-column">
<div class="form-row">
<div class="form-group">
<label for="first-name">First Name *</label>
<input type="text" id="first-name" name="first_name" required>
</div>
<div class="form-group">
<label for="last-name">Last Name *</label>
<input type="text" id="last-name" name="last_name" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="email">Email *</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone">
</div>
</div>
<div class="form-row">
<div class="form-group full-width">
<label for="address">Street Address</label>
<input type="text" id="address" name="address">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="city">City</label>
<input type="text" id="city" name="city">
</div>
<div class="form-group">
<label for="state">State</label>
<select id="state" name="state">
<option value="">Select state</option>
<option value="CA">California</option>
<option value="NY">New York</option>
<option value="TX">Texas</option>
</select>
</div>
<div class="form-group">
<label for="zip">ZIP Code</label>
<input type="text" id="zip" name="zip" pattern="[0-9]{5}">
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Submit</button>
<button type="reset" class="btn btn-secondary">Reset</button>
</div>
</form>
/* Two column form layout */
.form-two-column {
max-width: 800px;
margin: 0 auto;
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.form-row .form-group {
flex: 1;
margin-bottom: 0;
}
.form-group.full-width {
flex: 0 0 100%;
}
.form-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 30px;
}
/* Responsive */
@media (max-width: 768px) {
.form-row {
flex-direction: column;
gap: 0;
}
.form-row .form-group {
margin-bottom: 20px;
}
.form-actions {
flex-direction: column;
}
.form-actions .btn {
width: 100%;
}
}
Multi-step Form (Wizard)
<div class="multi-step-form">
<!-- Progress indicator -->
<div class="form-progress">
<div class="progress-step active" data-step="1">
<span class="step-number">1</span>
<span class="step-label">Personal Info</span>
</div>
<div class="progress-step" data-step="2">
<span class="step-number">2</span>
<span class="step-label">Account Details</span>
</div>
<div class="progress-step" data-step="3">
<span class="step-number">3</span>
<span class="step-label">Confirmation</span>
</div>
</div>
<!-- Form steps -->
<form id="multi-step-form">
<!-- Step 1 -->
<div class="form-step active" id="step-1">
<h3>Personal Information</h3>
<div class="form-group">
<label for="full-name">Full Name *</label>
<input type="text" id="full-name" name="full_name" required>
</div>
<div class="form-group">
<label for="email">Email *</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="phone">Phone Number</label>
<input type="tel" id="phone" name="phone">
</div>
<div class="form-navigation">
<button type="button" class="btn btn-primary next-step">Next</button>
</div>
</div>
<!-- Step 2 -->
<div class="form-step" id="step-2">
<h3>Account Details</h3>
<div class="form-group">
<label for="username">Username *</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password *</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<label for="confirm-password">Confirm Password *</label>
<input type="password" id="confirm-password" name="confirm_password" required>
</div>
<div class="form-navigation">
<button type="button" class="btn btn-secondary prev-step">Previous</button>
<button type="button" class="btn btn-primary next-step">Next</button>
</div>
</div>
<!-- Step 3 -->
<div class="form-step" id="step-3">
<h3>Review Your Information</h3>
<div class="review-section">
<h4>Personal Information</h4>
<p id="review-name"></p>
<p id="review-email"></p>
<p id="review-phone"></p>
</div>
<div class="review-section">
<h4>Account Details</h4>
<p id="review-username"></p>
<p id="review-password">β’β’β’β’β’β’β’β’</p>
</div>
<div class="form-navigation">
<button type="button" class="btn btn-secondary prev-step">Previous</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const steps = document.querySelectorAll('.form-step');
const progressSteps = document.querySelectorAll('.progress-step');
let currentStep = 0;
function showStep(stepIndex) {
// Hide all steps
steps.forEach(step => step.classList.remove('active'));
progressSteps.forEach(step => step.classList.remove('active'));
// Show current step
steps[stepIndex].classList.add('active');
progressSteps[stepIndex].classList.add('active');
// Update review section on last step
if (stepIndex === 2) {
document.getElementById('review-name').textContent =
document.getElementById('full-name').value;
document.getElementById('review-email').textContent =
document.getElementById('email').value;
document.getElementById('review-phone').textContent =
document.getElementById('phone').value || 'Not provided';
document.getElementById('review-username').textContent =
document.getElementById('username').value;
}
}
// Next button
document.querySelectorAll('.next-step').forEach(btn => {
btn.addEventListener('click', function() {
if (currentStep < steps.length - 1) {
// Validate current step
const currentStepInputs = steps[currentStep].querySelectorAll('input[required]');
let isValid = true;
currentStepInputs.forEach(input => {
if (!input.checkValidity()) {
input.reportValidity();
isValid = false;
}
});
if (isValid) {
currentStep++;
showStep(currentStep);
}
}
});
});
// Previous button
document.querySelectorAll('.prev-step').forEach(btn => {
btn.addEventListener('click', function() {
if (currentStep > 0) {
currentStep--;
showStep(currentStep);
}
});
});
// Form submit
document.getElementById('multi-step-form').addEventListener('submit', function(e) {
e.preventDefault();
alert('Form submitted successfully!');
});
});
</script>
/* Multi-step form styling */
.multi-step-form {
max-width: 600px;
margin: 0 auto;
padding: 30px;
background: white;
border-radius: 10px;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
}
.form-progress {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
position: relative;
}
.form-progress::before {
content: '';
position: absolute;
top: 20px;
left: 0;
right: 0;
height: 2px;
background: #e0e0e0;
z-index: 1;
}
.progress-step {
position: relative;
z-index: 2;
text-align: center;
flex: 1;
}
.step-number {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
background: #e0e0e0;
color: #666;
border-radius: 50%;
margin: 0 auto 10px;
font-weight: bold;
transition: all 0.3s;
}
.step-label {
font-size: 0.875rem;
color: #666;
}
.progress-step.active .step-number {
background: #4CAF50;
color: white;
}
.progress-step.active .step-label {
color: #4CAF50;
font-weight: 600;
}
.progress-step.completed .step-number {
background: #4CAF50;
color: white;
}
.form-step {
display: none;
}
.form-step.active {
display: block;
}
.form-navigation {
display: flex;
justify-content: space-between;
margin-top: 30px;
}
.review-section {
background: #f9f9f9;
padding: 20px;
border-radius: 6px;
margin-bottom: 20px;
}
.review-section h4 {
margin-top: 0;
margin-bottom: 15px;
color: #333;
}
.review-section p {
margin: 5px 0;
color: #666;
}
Custom Form Elements
Custom Range Slider
<div class="range-slider">
<label for="price">Price Range: $<span id="price-value">50</span></label>
<input type="range" id="price" name="price" min="0" max="100" value="50">
<div class="range-labels">
<span>$0</span>
<span>$25</span>
<span>$50</span>
<span>$75</span>
<span>$100</span>
</div>
</div>
<script>
document.getElementById('price').addEventListener('input', function() {
document.getElementById('price-value').textContent = this.value;
});
</script>
/* Custom range slider */
.range-slider {
margin: 20px 0;
}
.range-slider input[type=range] {
-webkit-appearance: none;
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
outline: none;
}
.range-slider input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: #4CAF50;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.range-slider input[type=range]::-webkit-slider-thumb:hover {
transform: scale(1.2);
}
.range-slider input[type=range]::-moz-range-thumb {
width: 20px;
height: 20px;
background: #4CAF50;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s;
border: none;
}
.range-slider input[type=range]::-moz-range-progress {
background: #4CAF50;
height: 8px;
border-radius: 4px;
}
.range-labels {
display: flex;
justify-content: space-between;
margin-top: 10px;
color: #666;
font-size: 0.875rem;
}
Custom Rating Stars
<div class="rating"> <p>Rate your experience:</p> <div class="stars"> <input type="radio" id="star5" name="rating" value="5"> <label for="star5" title="5 stars">β </label> <input type="radio" id="star4" name="rating" value="4"> <label for="star4" title="4 stars">β </label> <input type="radio" id="star3" name="rating" value="3"> <label for="star3" title="3 stars">β </label> <input type="radio" id="star2" name="rating" value="2"> <label for="star2" title="2 stars">β </label> <input type="radio" id="star1" name="rating" value="1"> <label for="star1" title="1 star">β </label> </div> </div>
/* Custom star rating */
.rating {
margin: 20px 0;
}
.stars {
display: inline-flex;
flex-direction: row-reverse;
gap: 5px;
}
.stars input {
display: none;
}
.stars label {
font-size: 30px;
color: #ddd;
cursor: pointer;
transition: color 0.2s;
}
.stars label:hover,
.stars label:hover ~ label {
color: #ffd700;
}
.stars input:checked ~ label {
color: #ffd700;
}
Custom Color Picker
<div class="color-picker-group">
<label for="color">Choose color:</label>
<div class="color-picker">
<input type="color" id="color" name="color" value="#4CAF50">
<span class="color-value">#4CAF50</span>
</div>
</div>
<script>
document.getElementById('color').addEventListener('input', function() {
document.querySelector('.color-value').textContent = this.value;
});
</script>
/* Custom color picker */
.color-picker {
display: flex;
align-items: center;
gap: 10px;
}
.color-picker input[type=color] {
width: 50px;
height: 50px;
border: 2px solid #e0e0e0;
border-radius: 6px;
cursor: pointer;
}
.color-value {
font-family: monospace;
padding: 0.5rem 1rem;
background: #f0f0f0;
border-radius: 4px;
font-size: 0.9rem;
}
Accessibility in Forms
ARIA Attributes
<!-- Required fields with ARIA --> <label for="username">Username <span aria-hidden="true">*</span></label> <input type="text" id="username" aria-required="true" aria-describedby="username-help username-error"> <div id="username-help" class="help-text"> Must be at least 3 characters, letters and numbers only </div> <div id="username-error" class="error-message" role="alert"></div> <!-- Fieldset for grouping --> <fieldset> <legend>Contact Preferences</legend> <div> <input type="checkbox" id="contact-email" name="contact_email"> <label for="contact-email">Email</label> </div> <div> <input type="checkbox" id="contact-phone" name="contact_phone"> <label for="contact-phone">Phone</label> </div> </fieldset> <!-- Error summary --> <div id="error-summary" class="error-summary" role="alert" tabindex="-1" aria-labelledby="error-heading"> <h3 id="error-heading">Please correct the following errors:</h3> <ul> <li><a href="#username">Username is required</a></li> <li><a href="#email">Email must be valid</a></li> </ul> </div> <!-- Progress indicator --> <div role="status" aria-live="polite"> Step 2 of 3: Account Details </div> <!-- Loading state --> <button aria-busy="true" aria-label="Submitting form..." disabled> <span class="spinner"></span> Submitting... </button>
Keyboard Navigation
<!-- Tab index for custom order --> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" tabindex="1"> </div> <div class="form-group"> <label for="email">Email</label> <input type="email" id="email" tabindex="2"> </div> <div class="form-group"> <label for="phone">Phone</label> <input type="tel" id="phone" tabindex="3"> </div> <button type="submit" tabindex="4">Submit</button> </form> <!-- Skip link for long forms --> <a href="#main-form" class="skip-link">Skip to form</a> <!-- Focus trap for modals --> <div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title"> <h2 id="modal-title">Login</h2> <div class="modal-content"> <!-- Form fields --> <button class="close-modal" aria-label="Close">Γ</button> </div> </div>
Screen Reader Announcements
<!-- Live regions for dynamic updates --> <div aria-live="polite" aria-atomic="true" class="sr-only"> <!-- Screen reader will announce changes --> </div> <!-- Password strength announcement --> <div role="status" aria-live="polite" id="password-strength-status"> Password strength: Weak </div> <!-- Form submission status --> <div role="status" aria-live="assertive" class="form-status"> <span class="success-message" aria-hidden="true">β</span> <span>Form submitted successfully!</span> </div>
Form Security
CSRF Protection
<!-- CSRF token in forms --> <form method="post" action="/submit"> <input type="hidden" name="csrf_token" value="random-generated-token"> <!-- form fields --> </form>
Input Sanitization Example
<!-- Client-side sanitization -->
<script>
function sanitizeInput(input) {
// Remove potentially dangerous characters
return input.replace(/[<>]/g, '');
}
document.getElementById('comment').addEventListener('input', function(e) {
this.value = sanitizeInput(this.value);
});
</script>
Rate Limiting Indicator
<!-- Rate limit indicator --> <div class="rate-limit"> <div class="attempts-left">3 attempts remaining</div> <div class="cooldown-timer" style="display: none;"> Too many attempts. Try again in <span id="timer">30</span> seconds </div> </div>
Practical Examples
Example 1: Complete Registration Form
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Registration Form</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.register-container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
width: 100%;
max-width: 500px;
padding: 40px;
}
.register-header {
text-align: center;
margin-bottom: 30px;
}
.register-header h1 {
color: #333;
font-size: 2rem;
margin-bottom: 10px;
}
.register-header p {
color: #666;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
font-size: 0.9rem;
}
label.required::after {
content: ' *';
color: #ff4444;
}
.input-wrapper {
position: relative;
}
.input-wrapper i {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #999;
}
input, select {
width: 100%;
padding: 12px 12px 12px 40px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
transition: all 0.3s;
}
input:focus, select:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
input::placeholder {
color: #aaa;
}
.password-requirements {
margin-top: 10px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
font-size: 0.85rem;
}
.password-requirements p {
margin-bottom: 10px;
color: #666;
font-weight: 500;
}
.password-requirements ul {
list-style: none;
}
.password-requirements li {
margin-bottom: 5px;
color: #999;
display: flex;
align-items: center;
gap: 5px;
}
.password-requirements li.valid {
color: #28a745;
}
.password-requirements li.invalid {
color: #dc3545;
}
.password-strength {
height: 4px;
background: #e0e0e0;
border-radius: 2px;
margin: 10px 0;
overflow: hidden;
}
.strength-bar {
height: 100%;
width: 0;
transition: all 0.3s;
}
.strength-bar.weak {
width: 33.33%;
background: #dc3545;
}
.strength-bar.medium {
width: 66.66%;
background: #ffc107;
}
.strength-bar.strong {
width: 100%;
background: #28a745;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 10px;
margin: 20px 0;
}
.checkbox-group input[type="checkbox"] {
width: auto;
padding: 0;
}
.checkbox-group label {
margin: 0;
font-weight: normal;
}
.checkbox-group a {
color: #667eea;
text-decoration: none;
}
.checkbox-group a:hover {
text-decoration: underline;
}
.btn {
width: 100%;
padding: 14px;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn:hover {
background: #5a67d8;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.btn:active {
transform: translateY(0);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.login-link {
text-align: center;
margin-top: 20px;
color: #666;
}
.login-link a {
color: #667eea;
text-decoration: none;
font-weight: 500;
}
.login-link a:hover {
text-decoration: underline;
}
.error-message {
color: #dc3545;
font-size: 0.85rem;
margin-top: 5px;
}
@media (max-width: 480px) {
.register-container {
padding: 20px;
}
}
</style>
</head>
<body>
<div class="register-container">
<div class="register-header">
<h1>Create Account</h1>
<p>Join our community today</p>
</div>
<form id="register-form">
<div class="form-group">
<label for="fullname" class="required">Full Name</label>
<div class="input-wrapper">
<i>π€</i>
<input type="text" id="fullname" name="fullname"
placeholder="Enter your full name" required>
</div>
</div>
<div class="form-group">
<label for="email" class="required">Email Address</label>
<div class="input-wrapper">
<i>βοΈ</i>
<input type="email" id="email" name="email"
placeholder="[email protected]" required>
</div>
</div>
<div class="form-group">
<label for="password" class="required">Password</label>
<div class="input-wrapper">
<i>π</i>
<input type="password" id="password" name="password"
placeholder="Create a password" required>
</div>
<div class="password-strength">
<div class="strength-bar" id="strength-bar"></div>
</div>
<div class="password-requirements" id="password-reqs">
<p>Password must contain:</p>
<ul>
<li id="req-length">β At least 8 characters</li>
<li id="req-lower">β At least 1 lowercase letter</li>
<li id="req-upper">β At least 1 uppercase letter</li>
<li id="req-number">β At least 1 number</li>
<li id="req-special">β At least 1 special character</li>
</ul>
</div>
</div>
<div class="form-group">
<label for="confirm-password" class="required">Confirm Password</label>
<div class="input-wrapper">
<i>π</i>
<input type="password" id="confirm-password" name="confirm_password"
placeholder="Re-enter your password" required>
</div>
<div id="password-match-error" class="error-message"></div>
</div>
<div class="checkbox-group">
<input type="checkbox" id="terms" name="terms" required>
<label for="terms">
I agree to the <a href="#">Terms of Service</a> and
<a href="#">Privacy Policy</a>
</label>
</div>
<button type="submit" class="btn" id="submit-btn">Create Account</button>
</form>
<div class="login-link">
Already have an account? <a href="#">Sign in</a>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('register-form');
const password = document.getElementById('password');
const confirm = document.getElementById('confirm-password');
const submitBtn = document.getElementById('submit-btn');
// Password strength checker
function checkPasswordStrength() {
const pwd = password.value;
const reqs = {
length: pwd.length >= 8,
lower: /[a-z]/.test(pwd),
upper: /[A-Z]/.test(pwd),
number: /\d/.test(pwd),
special: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(pwd)
};
// Update requirement indicators
for (const [req, met] of Object.entries(reqs)) {
const element = document.getElementById(`req-${req}`);
if (met) {
element.style.color = '#28a745';
element.innerHTML = 'β ' + element.innerHTML.substring(2);
} else {
element.style.color = '#999';
element.innerHTML = 'β ' + element.innerHTML.substring(2);
}
}
// Calculate strength
const metCount = Object.values(reqs).filter(Boolean).length;
const bar = document.getElementById('strength-bar');
if (metCount <= 2) {
bar.className = 'strength-bar weak';
} else if (metCount <= 4) {
bar.className = 'strength-bar medium';
} else {
bar.className = 'strength-bar strong';
}
}
// Password match checker
function checkPasswordMatch() {
const error = document.getElementById('password-match-error');
if (password.value !== confirm.value) {
error.textContent = 'Passwords do not match';
return false;
} else {
error.textContent = '';
return true;
}
}
// Real-time validation
password.addEventListener('input', checkPasswordStrength);
confirm.addEventListener('input', checkPasswordMatch);
// Form submission
form.addEventListener('submit', function(e) {
e.preventDefault();
// Check if form is valid
if (!form.checkValidity()) {
form.reportValidity();
return;
}
if (!checkPasswordMatch()) {
return;
}
// Show loading state
submitBtn.disabled = true;
submitBtn.textContent = 'Creating account...';
// Simulate API call
setTimeout(() => {
alert('Account created successfully!');
submitBtn.disabled = false;
submitBtn.textContent = 'Create Account';
form.reset();
}, 2000);
});
});
</script>
</body>
</html>
Example 2: Contact Form with Validation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact Form</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f0f2f5;
padding: 20px;
}
.contact-container {
max-width: 600px;
margin: 40px auto;
background: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
overflow: hidden;
}
.contact-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.contact-header h1 {
font-size: 2rem;
margin-bottom: 10px;
}
.contact-header p {
opacity: 0.9;
}
.contact-form {
padding: 30px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group.full-width {
grid-column: span 2;
}
label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
font-size: 0.9rem;
}
label::after {
content: ' *';
color: #ff4444;
display: inline;
}
label.optional::after {
content: ' (optional)';
color: #666;
font-weight: normal;
}
input, select, textarea {
width: 100%;
padding: 12px 15px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
transition: all 0.3s;
font-family: inherit;
}
input:focus, select:focus, textarea:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
input.error, select.error, textarea.error {
border-color: #ff4444;
}
.error-message {
color: #ff4444;
font-size: 0.85rem;
margin-top: 5px;
}
.success-message {
color: #28a745;
font-size: 0.85rem;
margin-top: 5px;
}
.radio-group, .checkbox-group {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.radio-option, .checkbox-option {
display: flex;
align-items: center;
gap: 8px;
}
.radio-option input, .checkbox-option input {
width: auto;
}
.radio-option label, .checkbox-option label {
margin: 0;
font-weight: normal;
}
.radio-option label::after, .checkbox-option label::after {
content: '';
}
.file-upload {
border: 2px dashed #e0e0e0;
border-radius: 8px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
}
.file-upload:hover {
border-color: #667eea;
background: #f8f9ff;
}
.file-upload input {
display: none;
}
.file-upload label {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
cursor: pointer;
margin: 0;
}
.file-upload i {
font-size: 40px;
color: #667eea;
}
.file-upload span {
color: #667eea;
font-weight: 500;
}
.file-info {
margin-top: 10px;
font-size: 0.9rem;
color: #666;
}
.form-actions {
display: flex;
gap: 15px;
margin-top: 30px;
}
.btn {
flex: 1;
padding: 14px;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a67d8;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #e0e0e0;
color: #333;
}
.btn-secondary:hover {
background: #d0d0d0;
}
@media (max-width: 768px) {
.form-row {
grid-template-columns: 1fr;
gap: 0;
}
.form-group.full-width {
grid-column: span 1;
}
.radio-group {
flex-direction: column;
gap: 10px;
}
.form-actions {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="contact-container">
<div class="contact-header">
<h1>Get in Touch</h1>
<p>We'd love to hear from you. Send us a message and we'll respond as soon as possible.</p>
</div>
<form class="contact-form" id="contactForm">
<div class="form-row">
<div class="form-group">
<label for="firstname">First Name</label>
<input type="text" id="firstname" name="firstname"
placeholder="John" required>
<div class="error-message" id="firstname-error"></div>
</div>
<div class="form-group">
<label for="lastname">Last Name</label>
<input type="text" id="lastname" name="lastname"
placeholder="Doe" required>
<div class="error-message" id="lastname-error"></div>
</div>
</div>
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email"
placeholder="[email protected]" required>
<div class="error-message" id="email-error"></div>
</div>
<div class="form-group">
<label for="phone">Phone Number <span class="optional">(optional)</span></label>
<input type="tel" id="phone" name="phone"
placeholder="(555) 123-4567">
<div class="error-message" id="phone-error"></div>
</div>
<div class="form-group">
<label for="subject">Subject</label>
<select id="subject" name="subject" required>
<option value="">Select a subject</option>
<option value="general">General Inquiry</option>
<option value="support">Technical Support</option>
<option value="billing">Billing Question</option>
<option value="partnership">Partnership Opportunity</option>
</select>
<div class="error-message" id="subject-error"></div>
</div>
<div class="form-group">
<label>Preferred Contact Method</label>
<div class="radio-group">
<div class="radio-option">
<input type="radio" id="contact-email" name="contact_method" value="email" checked>
<label for="contact-email">Email</label>
</div>
<div class="radio-option">
<input type="radio" id="contact-phone" name="contact_method" value="phone">
<label for="contact-phone">Phone</label>
</div>
<div class="radio-option">
<input type="radio" id="contact-any" name="contact_method" value="any">
<label for="contact-any">Any</label>
</div>
</div>
</div>
<div class="form-group full-width">
<label for="message">Message</label>
<textarea id="message" name="message" rows="5"
placeholder="How can we help you?" required></textarea>
<div class="error-message" id="message-error"></div>
<div class="character-counter">
<span id="charCount">0</span>/500 characters
</div>
</div>
<div class="form-group full-width">
<div class="file-upload" id="fileUpload">
<input type="file" id="attachment" name="attachment"
accept=".pdf,.doc,.docx,.jpg,.png">
<label for="attachment">
<i>π</i>
<span>Click to upload attachment</span>
<small>or drag and drop</small>
<small>PDF, DOC, JPG, PNG (max 5MB)</small>
</label>
</div>
<div class="file-info" id="fileInfo"></div>
</div>
<div class="checkbox-group">
<div class="checkbox-option">
<input type="checkbox" id="newsletter" name="newsletter">
<label for="newsletter">Subscribe to our newsletter</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="submitBtn">Send Message</button>
<button type="reset" class="btn btn-secondary">Clear Form</button>
</div>
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('contactForm');
const submitBtn = document.getElementById('submitBtn');
const message = document.getElementById('message');
const charCount = document.getElementById('charCount');
const fileInput = document.getElementById('attachment');
const fileInfo = document.getElementById('fileInfo');
// Character counter
message.addEventListener('input', function() {
const count = this.value.length;
charCount.textContent = count;
if (count > 450) {
charCount.style.color = '#ff4444';
} else if (count > 400) {
charCount.style.color = '#ffbb33';
} else {
charCount.style.color = '#666';
}
});
// File upload handling
fileInput.addEventListener('change', function() {
if (this.files.length > 0) {
const file = this.files[0];
const fileSize = (file.size / 1024 / 1024).toFixed(2);
fileInfo.textContent = `Selected: ${file.name} (${fileSize} MB)`;
} else {
fileInfo.textContent = '';
}
});
// Drag and drop highlight
const uploadArea = document.getElementById('fileUpload');
uploadArea.addEventListener('dragover', function(e) {
e.preventDefault();
this.style.borderColor = '#667eea';
this.style.background = '#f8f9ff';
});
uploadArea.addEventListener('dragleave', function(e) {
e.preventDefault();
this.style.borderColor = '#e0e0e0';
this.style.background = 'white';
});
uploadArea.addEventListener('drop', function(e) {
e.preventDefault();
this.style.borderColor = '#e0e0e0';
this.style.background = 'white';
const files = e.dataTransfer.files;
if (files.length > 0) {
fileInput.files = files;
const file = files[0];
const fileSize = (file.size / 1024 / 1024).toFixed(2);
fileInfo.textContent = `Selected: ${file.name} (${fileSize} MB)`;
}
});
// Form validation
function validateField(field) {
const errorElement = document.getElementById(field.id + '-error');
if (!field.checkValidity()) {
field.classList.add('error');
if (field.validity.valueMissing) {
errorElement.textContent = 'This field is required';
} else if (field.validity.typeMismatch) {
errorElement.textContent = 'Please enter a valid ' + field.type;
} else if (field.validity.tooShort) {
errorElement.textContent = 'Minimum length is ' + field.minLength + ' characters';
} else {
errorElement.textContent = field.validationMessage;
}
return false;
} else {
field.classList.remove('error');
errorElement.textContent = '';
return true;
}
}
// Real-time validation
form.querySelectorAll('input, select, textarea').forEach(field => {
field.addEventListener('blur', function() {
validateField(this);
});
field.addEventListener('input', function() {
if (this.classList.contains('error')) {
validateField(this);
}
});
});
// Form submission
form.addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
// Validate all fields
form.querySelectorAll('input[required], select[required], textarea[required]').forEach(field => {
if (!validateField(field)) {
isValid = false;
}
});
// Validate email format
const email = document.getElementById('email');
if (email.value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)) {
document.getElementById('email-error').textContent = 'Please enter a valid email address';
email.classList.add('error');
isValid = false;
}
// Validate phone if provided
const phone = document.getElementById('phone');
if (phone.value && !/^[\d\s\-\(\)]+$/.test(phone.value)) {
document.getElementById('phone-error').textContent = 'Please enter a valid phone number';
phone.classList.add('error');
isValid = false;
}
if (isValid) {
// Show loading state
submitBtn.disabled = true;
submitBtn.textContent = 'Sending...';
// Simulate form submission
setTimeout(() => {
alert('Message sent successfully! We\'ll get back to you soon.');
form.reset();
charCount.textContent = '0';
fileInfo.textContent = '';
submitBtn.disabled = false;
submitBtn.textContent = 'Send Message';
// Clear error states
form.querySelectorAll('.error').forEach(field => {
field.classList.remove('error');
});
form.querySelectorAll('.error-message').forEach(msg => {
msg.textContent = '';
});
}, 2000);
}
});
// Reset button
form.querySelector('button[type="reset"]').addEventListener('click', function() {
setTimeout(() => {
charCount.textContent = '0';
fileInfo.textContent = '';
// Clear error states
form.querySelectorAll('.error').forEach(field => {
field.classList.remove('error');
});
form.querySelectorAll('.error-message').forEach(msg => {
msg.textContent = '';
});
}, 0);
});
});
</script>
</body>
</html>
Best Practices
Form Design Guidelines
/* Form design best practices */
.form-best-practices {
max-width: 600px;
margin: 0 auto;
}
/* 1. Clear visual hierarchy */
.form-section {
margin-bottom: 30px;
padding: 20px;
background: #f9f9f9;
border-radius: 8px;
}
.form-section h3 {
margin-top: 0;
margin-bottom: 20px;
color: #333;
font-size: 1.25rem;
}
/* 2. Consistent spacing */
.form-group {
margin-bottom: 20px;
}
/* 3. Clear labels */
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #333;
}
/* 4. Adequate touch targets (mobile) */
@media (max-width: 768px) {
input, select, textarea, button {
min-height: 44px; /* Apple's recommended minimum */
}
.checkbox-group label,
.radio-group label {
min-height: 44px;
display: flex;
align-items: center;
}
}
/* 5. Error states clearly visible */
.error-message {
color: #dc3545;
font-size: 0.875rem;
margin-top: 5px;
display: flex;
align-items: center;
gap: 5px;
}
.error-message::before {
content: 'β οΈ';
font-size: 0.875rem;
}
/* 6. Success states */
.success-message {
color: #28a745;
font-size: 0.875rem;
margin-top: 5px;
display: flex;
align-items: center;
gap: 5px;
}
.success-message::before {
content: 'β
';
}
/* 7. Loading states */
.btn.loading {
position: relative;
color: transparent;
}
.btn.loading::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
top: 50%;
left: 50%;
margin-left: -10px;
margin-top: -10px;
border: 2px solid white;
border-top-color: transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 8. Disabled states */
input:disabled,
select:disabled,
textarea:disabled,
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* 9. Focus indicators for accessibility */
:focus {
outline: 3px solid rgba(102, 126, 234, 0.4);
outline-offset: 2px;
}
/* 10. Help text */
.help-text {
color: #666;
font-size: 0.875rem;
margin-top: 5px;
}
Form Security Checklist
<!-- Form security checklist --> <div class="security-checklist"> <h3>Form Security Checklist</h3> <ul> <li> <input type="checkbox" checked disabled> β Use POST method for sensitive data </li> <li> <input type="checkbox" checked disabled> β Implement CSRF tokens </li> <li> <input type="checkbox" checked disabled> β Validate input on server-side </li> <li> <input type="checkbox" checked disabled> β Sanitize user input </li> <li> <input type="checkbox" checked disabled> β Use HTTPS for form submission </li> <li> <input type="checkbox" checked disabled> β Implement rate limiting </li> <li> <input type="checkbox" checked disabled> β Set proper input length limits </li> <li> <input type="checkbox" checked disabled> β Escape output to prevent XSS </li> <li> <input type="checkbox" checked disabled> β Use prepared statements for databases </li> <li> <input type="checkbox" checked disabled> β Implement CAPTCHA for public forms </li> </ul> </div>
Accessibility Checklist
<!-- Accessibility checklist --> <div class="a11y-checklist"> <h3>Accessibility Checklist</h3> <ul> <li> <input type="checkbox" checked disabled> β All inputs have associated labels </li> <li> <input type="checkbox" checked disabled> β Form fields are keyboard accessible </li> <li> <input type="checkbox" checked disabled> β Focus indicators are visible </li> <li> <input type="checkbox" checked disabled> β Error messages are announced to screen readers </li> <li> <input type="checkbox" checked disabled> β Required fields are indicated </li> <li> <input type="checkbox" checked disabled> β Form uses semantic HTML </li> <li> <input type="checkbox" checked disabled> β Color is not the only means of conveying information </li> <li> <input type="checkbox" checked disabled> β Sufficient color contrast for text </li> <li> <input type="checkbox" checked disabled> β Form fields are grouped logically </li> <li> <input type="checkbox" checked disabled> β ARIA attributes used when necessary </li> </ul> </div>
Conclusion
Forms are a critical component of web development, serving as the primary means of user interaction and data collection. This comprehensive guide covered:
Key Takeaways
- HTML Form Elements: Understanding all input types and their purposes
- Form Validation: Both built-in HTML5 and custom JavaScript validation
- CSS Styling: Creating beautiful, user-friendly form designs
- Accessibility: Making forms usable for everyone
- Security: Protecting user data and preventing attacks
- User Experience: Designing forms that are easy to use
- Responsive Design: Forms that work on all devices
- Real-time Validation: Providing immediate feedback
- Custom Components: Building unique form elements
- Best Practices: Following industry standards
Next Steps
- Practice building different types of forms
- Implement server-side validation
- Learn about form security in depth
- Study UX patterns for complex forms
- Experiment with CSS frameworks for forms
- Test forms with screen readers
- Optimize form performance
Remember: A well-designed form can significantly improve user experience and conversion rates. Take the time to understand your users' needs and design forms that are both functional and delightful to use!
HTML & CSS Learning Guides, Exercises, Projects, Design Systems, Accessibility & Performance (Related to HTML & CSS Development)
HTML & CSS Quiz β Comprehensive Assessment:
This quiz evaluates core knowledge of HTML and CSS including structure, styling, layout, forms, and responsive design. It is used to test practical understanding of front-end fundamentals and identify skill levels in web development.
Read more: https://macronepal.com/bash/html-and-css-quiz-comprehensive-assessment/
Complete Guide to HTML & CSS Tooling & Automation:
This guide explains tools and automation workflows used in modern web development, such as preprocessors, build tools, and task runners that improve efficiency in HTML and CSS projects.
Read more: https://macronepal.com/bash/complete-guide-to-html-and-css-tooling-automation/
Complete HTML & CSS Exercises:
A collection of practical exercises designed to strengthen HTML and CSS skills through hands-on coding tasks, covering layout, styling, and responsive design concepts.
Read more: https://macronepal.com/bash/complete-html-and-css-exercises/
Complete HTML & CSS Landing Page Project:
This project focuses on building a full landing page using HTML and CSS, helping learners understand real-world website structure, layout design, and UI development.
Read more: https://macronepal.com/bash/complete-html-css-landing-page-project/
HTML & CSS Debugging and Testing Guide:
This guide teaches how to identify and fix errors in HTML and CSS code, along with testing techniques to ensure websites work correctly across browsers.
Read more: https://macronepal.com/bash/complete-guide-to-html-and-css-debugging-testing/
HTML & CSS Design Systems Guide:
A design system approach helps maintain consistency in UI development using reusable components, styles, and patterns across web projects.
Read more: https://macronepal.com/html-and-css/complete-guide-to-html-and-css-design-systems/
HTML & CSS Accessibility (A11y) Guide:
This guide focuses on making websites accessible for all users, including proper semantic HTML, keyboard navigation, alt text, and screen reader support.
Read more: https://macronepal.com/bash/complete-guide-to-html-css-accessibility-a11y/
HTML & CSS Performance Guide:
This topic explains optimization techniques such as reducing file size, improving loading speed, and writing efficient HTML and CSS for better website performance.
Read more: https://macronepal.com/bash/complete-guide-to-html-and-css-performance/
HTML & CSS Design Systems (Advanced Overview):
Design systems help developers maintain scalable and consistent UI components across large projects using structured HTML and reusable CSS patterns.
Read more: https://macronepal.com/html-and-css/complete-guide-to-html-and-css-design-systems/