<!-- HTML: Required fields --><form id="signupForm"> <label for="email">Email (required):</label> <input type="email" id="email" name="email" required> <label for="username">Username (required):</label> <input type="text" id="username" name="username" required minlength="3"> <label for="age">Age (optional):</label> <input type="number" id="age" name="age" min="13"> <button type="submit">Sign Up</button></form><!-- CSS: Validation state styling --><style> /* Style all required fields */ input:required { border-left: 3px solid #ff9800; } /* Valid state (avoid on page load) */ input:not(:placeholder-shown):valid { border-color: #4caf50; background-image: url('check-icon.svg'); background-repeat: no-repeat; background-position: right 10px center; } /* Invalid state (only after user interaction) */ input:user-invalid, input:not(:placeholder-shown):invalid { border-color: #f44336; background-color: #ffebee; } /* Focus states */ input:invalid:focus { outline: 2px solid #f44336; } input:valid:focus { outline: 2px solid #4caf50; } /* Optional fields (subtle styling) */ input:optional { border-left: 3px solid #ccc; }</style><!-- JavaScript: Check validity --><script> const form = document.getElementById('signupForm'); const email = document.getElementById('email'); form.addEventListener('submit', (e) => { if (!form.checkValidity()) { e.preventDefault(); alert('Please fill in all required fields correctly.'); } }); // Check individual field validity email.addEventListener('blur', () => { const validity = email.validity; if (validity.valueMissing) { console.log('Email is required'); } else if (validity.typeMismatch) { console.log('Invalid email format'); } else if (validity.valid) { console.log('Email is valid'); } });</script>
Warning::invalid triggers immediately on page load for empty required fields. Use
:user-invalid or :not(:placeholder-shown):invalid to only style after user
interaction.
<!-- ZIP Code --><label for="zip">ZIP Code:</label><input type="text" id="zip" name="zip" pattern="[0-9]{5}" title="Please enter a 5-digit ZIP code" placeholder="12345" required><!-- Phone Number --><label for="phone">Phone:</label><input type="tel" id="phone" name="phone" pattern="\d{3}-\d{3}-\d{4}" title="Format: 555-123-4567" placeholder="555-123-4567"><!-- Username --><label for="username">Username:</label><input type="text" id="username" name="username" pattern="[a-zA-Z0-9_]{3,16}" title="3-16 characters: letters, numbers, and underscores only" placeholder="user_name" required><!-- Strong Password --><label for="password">Password:</label><input type="password" id="password" name="password" pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$" title="Minimum 8 characters, at least one uppercase, lowercase, number, and special character" required><!-- Credit Card --><label for="card">Credit Card:</label><input type="text" id="card" name="card" pattern="\d{4}[\s\-]?\d{4}[\s\-]?\d{4}[\s\-]?\d{4}" title="Enter 16-digit card number" placeholder="1234-5678-9012-3456" maxlength="19"><!-- URL Slug --><label for="slug">Article Slug:</label><input type="text" id="slug" name="slug" pattern="[a-z0-9]+(?:-[a-z0-9]+)*" title="Lowercase letters, numbers, and hyphens only" placeholder="my-article-title"><!-- Hex Color --><label for="color">Hex Color:</label><input type="text" id="color" name="color" pattern="#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})" title="Enter hex color code (e.g., #ff5733 or #fff)" placeholder="#ff5733"><!-- Custom validation message (JavaScript) --><script> const usernameInput = document.getElementById('username'); usernameInput.addEventListener('invalid', (e) => { if (usernameInput.validity.patternMismatch) { usernameInput.setCustomValidity('Username must be 3-16 characters and contain only letters, numbers, and underscores.'); } else { usernameInput.setCustomValidity(''); } }); usernameInput.addEventListener('input', () => { usernameInput.setCustomValidity(''); });</script>
Note: Always include title attribute with pattern - it's shown in the
validation message. Regex in pattern is automatically anchored (^...$), so don't add anchors. Use
setCustomValidity() for better error messages.
Warning: When using setCustomValidity(), you must clear it with
setCustomValidity('') when the field becomes valid, or the field will remain invalid even if the
constraint is satisfied.
4. HTML5 Input Constraints and Limits
Constraint
Attribute
Applies To
Example
Required
required
Most inputs, select, textarea
<input required>
Min Length
minlength="N"
text, email, url, tel, password, search, textarea
<input minlength="3">
Max Length
maxlength="N"
text, email, url, tel, password, search, textarea
<input maxlength="50">
Min Value
min="N"
number, range, date, time, datetime-local, month, week
<input type="number" min="0">
Max Value
max="N"
number, range, date, time, datetime-local, month, week
<input type="number" max="100">
Step
step="N"
number, range, date, time, datetime-local, month, week
<input type="number" step="0.01">
Pattern
pattern="regex"
text, email, url, tel, password, search
<input pattern="[0-9]{5}">
Accept
accept="types"
file
<input type="file" accept="image/*">
Multiple
multiple
email, file, select
<input type="email" multiple>
Step Values
Step
Use Case
1
Integers only (default)
0.01
Decimal values (currency)
0.1
Single decimal place
5
Multiples of 5
any
No step constraint
900 (time)
15-minute intervals
Accept MIME Types
Value
Accepts
image/*
Any image type
video/*
Any video type
audio/*
Any audio type
.pdf
PDF files only
.jpg,.png
Specific extensions
image/png
Specific MIME type
Example: Input constraints in practice
<!-- Length constraints --><label for="username">Username (3-15 characters):</label><input type="text" id="username" name="username" minlength="3" maxlength="15" required><!-- Number range with step --><label for="price">Price ($):</label><input type="number" id="price" name="price" min="0" max="10000" step="0.01" placeholder="0.00"><!-- Age restriction --><label for="age">Age (must be 18+):</label><input type="number" id="age" name="age" min="18" max="120" required><!-- Date range (current year only) --><label for="event-date">Event Date:</label><input type="date" id="event-date" name="event-date" min="2024-01-01" max="2024-12-31" required><!-- Time with 15-minute intervals --><label for="appointment">Appointment Time:</label><input type="time" id="appointment" name="appointment" min="09:00" max="17:00" step="900"> <!-- 900 seconds = 15 min --><!-- Percentage (0-100 with 0.1 precision) --><label for="discount">Discount (%):</label><input type="number" id="discount" name="discount" min="0" max="100" step="0.1"><!-- File upload (images only) --><label for="avatar">Profile Picture:</label><input type="file" id="avatar" name="avatar" accept="image/png, image/jpeg, image/webp" required><!-- Multiple file upload --><label for="documents">Upload Documents:</label><input type="file" id="documents" name="documents" accept=".pdf,.doc,.docx" multiple><!-- Multiple emails --><label for="recipients">Email Recipients:</label><input type="email" id="recipients" name="recipients" multiple placeholder="email1@example.com, email2@example.com"><!-- Range slider with step --><label for="volume">Volume (0-100, steps of 5):</label><input type="range" id="volume" name="volume" min="0" max="100" step="5" value="50"><output for="volume">50</output><!-- No step constraint (any decimal) --><label for="precise">Precise Value:</label><input type="number" id="precise" name="precise" step="any">
Note:maxlength prevents typing beyond the limit, while minlength
only validates on submit. Use step="any" to allow any decimal value. For time inputs, step is in
seconds (900 = 15 minutes).
5. Form Submission and Method Handling
Method
Use Case
Data in URL
Cacheable
GET
Search, filter, idempotent operations
Yes (query string)
Yes
POST
Create, update, delete, file uploads, sensitive data
No (request body)
No
DIALOG
Close dialog without submission
N/A
N/A
Form Submission Events
Event
When Fired
submit
Form submitted (before send)
formdata
FormData object created
invalid
Validation fails on field
reset
Form reset triggered
Submit Methods (JS)
Method
Description
form.submit()
Submit programmatically (no validation)
form.requestSubmit()
Submit with validation (fires submit event)
form.reset()
Reset to default values
new FormData(form)
Extract form data as FormData object
Example: Form submission handling
<!-- GET form (search) --><form action="/search" method="GET"> <input type="search" name="q" placeholder="Search..."> <input type="checkbox" name="category" value="products"> Products <input type="checkbox" name="category" value="articles"> Articles <button type="submit">Search</button></form><!-- Submits to: /search?q=keyword&category=products&category=articles --><!-- POST form (user registration) --><form action="/register" method="POST" id="registerForm"> <input type="text" name="username" required> <input type="email" name="email" required> <input type="password" name="password" required> <button type="submit">Register</button></form><!-- JavaScript: Intercept and handle submission --><script> const form = document.getElementById('registerForm'); // Method 1: Prevent default and use Fetch API form.addEventListener('submit', async (e) => { e.preventDefault(); // Prevent traditional form submission // Get form data const formData = new FormData(form); // Convert to JSON (if needed) const data = Object.fromEntries(formData.entries()); try { const response = await fetch('/register', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }); if (response.ok) { const result = await response.json(); console.log('Success:', result); // Redirect or show success message } else { console.error('Error:', response.statusText); } } catch (error) { console.error('Network error:', error); } }); // Method 2: FormData with all form fields form.addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(form); // Send as multipart/form-data (good for file uploads) const response = await fetch('/register', { method: 'POST', body: formData, // Browser sets correct Content-Type }); }); // Method 3: Programmatic submission function submitForm() { if (form.checkValidity()) { // Triggers validation and submit event form.requestSubmit(); } else { // Show validation errors form.reportValidity(); } }</script><!-- Multiple submit buttons with different actions --><form method="POST"> <input type="text" name="content" required> <button type="submit" name="action" value="save">Save Draft</button> <button type="submit" name="action" value="publish">Publish</button> <button type="submit" name="action" value="delete" formnovalidate>Delete</button></form><!-- Override form attributes on button --><form action="/default" method="POST"> <input type="email" name="email" required> <button type="submit">Submit to Default</button> <button type="submit" formaction="/alternative" formmethod="GET" formnovalidate> Submit to Alternative (Skip Validation) </button></form>
Warning:form.submit() bypasses validation and doesn't fire the submit event. Use
form.requestSubmit() instead to trigger validation. Use formnovalidate on submit
buttons to skip validation for actions like "Save Draft" or "Delete".
6. Form Data Encoding and File Uploads
Encoding Type (enctype)
Value
Use Case
Content-Type Header
URL Encoded
application/x-www-form-urlencoded
Standard forms (default)
application/x-www-form-urlencoded
Multipart
multipart/form-data
File uploads
multipart/form-data; boundary=...
Plain Text
text/plain
Debugging (not recommended)
text/plain
File Input Attributes
Attribute
Purpose
accept
Filter file types in picker
multiple
Allow multiple file selection
capture
Use camera (mobile): user, environment
files
FileList object (read-only, JS)
FormData Methods
Method
Description
append()
Add field (allows duplicates)
set()
Set field (overwrites existing)
get()
Get single value
getAll()
Get all values for key
delete()
Remove field
has()
Check if field exists
Example: File uploads and form data encoding
<!-- File upload form --><form action="/upload" method="POST" enctype="multipart/form-data" id="uploadForm"> <label for="file">Select File:</label> <input type="file" id="file" name="file" accept="image/*" required> <label for="description">Description:</label> <input type="text" id="description" name="description"> <button type="submit">Upload</button></form><!-- Multiple file upload with preview --><form id="multiUploadForm"> <input type="file" id="files" name="files" multiple accept="image/*"> <div id="preview"></div> <button type="submit">Upload All</button></form><script> // Single file upload const uploadForm = document.getElementById('uploadForm'); uploadForm.addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(uploadForm); try { const response = await fetch('/upload', { method: 'POST', body: formData, // Browser automatically sets multipart/form-data }); if (response.ok) { const result = await response.json(); console.log('Upload successful:', result); } } catch (error) { console.error('Upload failed:', error); } }); // Multiple files with preview const filesInput = document.getElementById('files'); const preview = document.getElementById('preview'); filesInput.addEventListener('change', (e) => { preview.innerHTML = ''; // Clear previous previews const files = e.target.files; for (let i = 0; i < files.length; i++) { const file = files[i]; // Validate file if (!file.type.startsWith('image/')) { alert(`${file.name} is not an image`); continue; } if (file.size > 5 * 1024 * 1024) { // 5MB limit alert(`${file.name} is too large (max 5MB)`); continue; } // Create preview const reader = new FileReader(); reader.onload = (event) => { const img = document.createElement('img'); img.src = event.target.result; img.style.width = '100px'; img.style.margin = '5px'; preview.appendChild(img); }; reader.readAsDataURL(file); } }); // Multi-file upload const multiUploadForm = document.getElementById('multiUploadForm'); multiUploadForm.addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(); const files = filesInput.files; // Add each file for (let i = 0; i < files.length; i++) { formData.append('files', files[i]); } // Add additional data formData.append('userId', '12345'); formData.append('category', 'photos'); const response = await fetch('/upload-multiple', { method: 'POST', body: formData, }); }); // Manual FormData construction const manualFormData = new FormData(); // Add text fields manualFormData.append('username', 'john_doe'); manualFormData.append('email', 'john@example.com'); // Add file (from input element) const fileInput = document.getElementById('file'); manualFormData.append('avatar', fileInput.files[0]); // Add file (blob/programmatically) const blob = new Blob(['Hello, world!'], { type: 'text/plain' }); manualFormData.append('note', blob, 'note.txt'); // Get values console.log(manualFormData.get('username')); // 'john_doe' console.log(manualFormData.getAll('username')); // ['john_doe'] // Check if field exists console.log(manualFormData.has('email')); // true // Delete field manualFormData.delete('note'); // Iterate over entries for (const [key, value] of manualFormData.entries()) { console.log(`${key}:`, value); }</script><!-- Camera capture (mobile) --><form> <!-- Use front camera --> <input type="file" accept="image/*" capture="user"> <!-- Use back camera --> <input type="file" accept="image/*" capture="environment"> <!-- Video capture --> <input type="file" accept="video/*" capture></form>
Note: Always set enctype="multipart/form-data" for file uploads. When using
FormData with fetch(), don't set Content-Type header - browser adds it automatically with boundary. Use
capture attribute on mobile to directly access camera.
Section 8 Key Takeaways
Use :user-invalid or :not(:placeholder-shown):invalid to avoid styling empty
fields as invalid
Always include title with pattern for user-friendly error messages
Use setCustomValidity() for custom validation, but remember to clear it with
setCustomValidity('')
Use form.requestSubmit() instead of form.submit() to trigger validation
maxlength prevents typing, minlength validates on submit
Set enctype="multipart/form-data" for file uploads
Use formnovalidate on buttons for actions that should skip validation (e.g., "Save Draft")
FormData automatically handles multipart encoding - don't manually set Content-Type when using fetch()