|$ curl https://forge-ai.dev/api/markdown?path=docs/html/forms
$cat docs/html-forms.md
updated This week·55 min read·published

HTML Forms

HTMLFormsIntermediateIntermediate
Introduction

HTML forms are the primary mechanism for collecting user input and submitting it to a server for processing. A form wraps one or more form controls — inputs, buttons, selects, textareas — and defines how, where, and with what constraints the data is sent.

The <form> element acts as the container. Its attributes control submission behavior: action defines the endpoint URL, method selects GET or POST, enctype specifies the encoding for file uploads, and novalidate disables built-in validation.

Form Attributes
AttributeValuesDescription
actionURLURL where form data is sent on submission
methodGET | POSTHTTP method for the request
enctypeapplication/x-www-form-urlencoded | multipart/form-data | text/plainMIME type for encoding form data
novalidatebooleanDisables browser validation on submit
target_self | _blank | _parent | _topWhere to display the response
autocompleteon | offBrowser autofill behavior
namestringIdentifies the form in DOM
FeatureGETPOST
Data visibilityAppended to URL (query string)Sent in request body (hidden)
BookmarkableYes — URL contains all dataNo
Data size limit~2KB (URL length limit)No practical limit
SecurityNot for sensitive data (visible in URL)Data in body — use HTTPS
File uploadNot supportedSupported (multipart/form-data)
IdempotentYes — safe for repeated requestsNo — may create side effects
Use caseSearch, filters, paginationLogin, registration, file upload
form-attributes.html
HTML
1<!-- GET form — data in URL query string -->
2<form action="/search" method="GET">
3 <label for="q">Search:</label>
4 <input type="search" id="q" name="q" />
5 <button type="submit">Search</button>
6</form>
7
8<!-- POST form — data in request body -->
9<form action="/signup" method="POST" enctype="application/x-www-form-urlencoded">
10 <label for="email">Email:</label>
11 <input type="email" id="email" name="email" required />
12 <button type="submit">Create Account</button>
13</form>
14
15<!-- File upload — requires multipart/form-data -->
16<form action="/upload" method="POST" enctype="multipart/form-data">
17 <label for="file">Choose file:</label>
18 <input type="file" id="file" name="file" />
19 <button type="submit">Upload</button>
20</form>
21
22<!-- No validation — novalidate attribute -->
23<form action="/submit" method="POST" novalidate>
24 <input type="email" name="email" />
25 <button type="submit">Submit (no validation)</button>
26</form>

best practice

Use GET for read-only operations like search and filtering. Use POST for operations that change server state — mutations, authentication, file uploads. Never send sensitive data via GET; the query string is logged by browsers, proxies, and servers.
Form Controls

HTML5 defines over twenty input types, each optimized for a specific kind of data. Browsers render native controls — date pickers, color pickers, sliders — without JavaScript. The <input> element is self-closing and uses the type attribute to determine its behavior and appearance.

TypeHTMLDescription
text<input type="text">Single-line text input (default)
email<input type="email">Email address with built-in format validation
password<input type="password">Masked text entry
number<input type="number">Numeric input with step/range controls
tel<input type="tel">Telephone number (no validation pattern enforced)
url<input type="url">URL with built-in format validation
search<input type="search">Search field with clear button
date<input type="date">Date picker (YYYY-MM-DD)
time<input type="time">Time picker (HH:MM)
datetime-local<input type="datetime-local">Date + time picker (no timezone)
month<input type="month">Month and year picker
week<input type="week">Week and year picker
color<input type="color">Color picker (hexadecimal value)
range<input type="range">Slider control (min, max, step, value)
file<input type="file">File picker (accept, multiple, capture)
checkbox<input type="checkbox">Boolean toggle (multiple allowed)
radio<input type="radio">Single-select from group (same name)
hidden<input type="hidden">Invisible field for passing data
image<input type="image">Image as submit button (src, alt)
submit<input type="submit">Submit button (value sets label)
reset<input type="reset">Resets form to initial values
button<input type="button">Clickable button (no default behavior)

Live preview of various input types rendered in a form layout:

preview
Text Inputs

Text inputs and textareas are the most common form controls. The <input type="text"> handles single-line input, while <textarea> supports multi-line text entry with configurable dimensions.

AttributetexttextareaDescription
placeholderHint text shown before input
maxlengthMaximum character count
minlengthMinimum character count
patternRegex validation
readonlyNot editable, but submitted
disabledNot editable, not submitted, grayed
spellcheckEnable/disable spell checking
autocompleteBrowser autofill behavior
rowsVisible textarea height (lines)
colsVisible textarea width (chars)
wrapLine wrapping (soft|hard|off)
text-inputs.html
HTML
1<!-- Single-line text input with validation -->
2<input
3 type="text"
4 id="username"
5 name="username"
6 placeholder="johndoe"
7 minlength="3"
8 maxlength="20"
9 pattern="[a-zA-Z0-9_]+"
10 required
11 autocomplete="username"
12/>
13
14<!-- Multi-line textarea -->
15<textarea
16 id="bio"
17 name="bio"
18 rows="6"
19 cols="50"
20 maxlength="500"
21 placeholder="Tell us about yourself..."
22 wrap="soft"
23></textarea>
24
25<!-- Readonly and disabled examples -->
26<input type="text" value="Pre-filled, not editable" readonly />
27<input type="text" value="Grayed out, not submitted" disabled />
28
29<!-- Email with custom pattern -->
30<input
31 type="email"
32 id="work-email"
33 name="work-email"
34 placeholder="name@company.com"
35 pattern="[a-z0-9._%+\-]+@[a-z0-9.\-]+.[a-z]{2,}$"
36 title="Enter a valid email address"
37/>
🔥

pro tip

The pattern attribute uses JavaScript regex syntax. Always provide a matching title attribute to describe the expected format, as it is used in validation error messages.

Live preview of text inputs with various states:

preview
Select Menus

Select menus present a list of options in a dropdown. The <select> element creates the menu, <option> defines each choice, and <optgroup> organizes options into labeled groups. The <datalist> element provides an autocomplete suggestion list for text inputs.

Element / AttributeDescription
<select>Dropdown menu container
<option>Individual menu item (value + label)
<optgroup>Groups options under a label heading
<datalist>Autocomplete suggestions for <input>
multipleAllows selecting multiple options
sizeNumber of visible options (expands into list)
selectedPre-selects an option
disabledDisables an option or the entire select
select-menus.html
HTML
1<!-- Basic select -->
2<label for="country">Country:</label>
3<select id="country" name="country">
4 <option value="">— Select a country —</option>
5 <option value="us">United States</option>
6 <option value="ca">Canada</option>
7 <option value="uk">United Kingdom</option>
8 <option value="de">Germany</option>
9 <option value="jp" selected>Japan</option>
10</select>
11
12<!-- Select with option groups -->
13<label for="department">Department:</label>
14<select id="department" name="department">
15 <optgroup label="Engineering">
16 <option value="frontend">Frontend</option>
17 <option value="backend">Backend</option>
18 <option value="devops">DevOps</option>
19 </optgroup>
20 <optgroup label="Design">
21 <option value="ui">UI Design</option>
22 <option value="ux">UX Research</option>
23 </optgroup>
24 <optgroup label="Business">
25 <option value="sales">Sales</option>
26 <option value="marketing">Marketing</option>
27 </optgroup>
28</select>
29
30<!-- Multi-select list -->
31<label for="skills">Skills (hold Ctrl/Cmd to select multiple):</label>
32<select id="skills" name="skills" multiple size="5">
33 <option value="html">HTML</option>
34 <option value="css">CSS</option>
35 <option value="js">JavaScript</option>
36 <option value="ts" selected>TypeScript</option>
37 <option value="react">React</option>
38 <option value="node">Node.js</option>
39</select>
40
41<!-- Datalist for autocomplete -->
42<label for="browser">Choose a browser:</label>
43<input list="browsers" id="browser" name="browser" placeholder="Start typing..." />
44<datalist id="browsers">
45 <option value="Chrome" />
46 <option value="Firefox" />
47 <option value="Safari" />
48 <option value="Edge" />
49 <option value="Opera" />
50 <option value="Brave" />
51</datalist>

info

Datalists are not restricted to <select> behavior — they can suggest values for any text input. Unlike <select>, the user can still type a custom value not in the list.

Live preview of select menus and datalist:

preview
Checkboxes & Radios

Checkboxes allow multiple independent selections. Radio buttons restrict the user to exactly one choice within a group. Both use the checked attribute for preselection and the name attribute for grouping — radio buttons with the same name form a single group where only one can be selected.

checkboxes-radios.html
HTML
1<!-- Checkboxes — independent choices -->
2<fieldset>
3 <legend>Interests (select all that apply)</legend>
4
5 <label>
6 <input type="checkbox" name="interests" value="coding" checked />
7 Coding
8 </label>
9 <label>
10 <input type="checkbox" name="interests" value="design" />
11 Design
12 </label>
13 <label>
14 <input type="checkbox" name="interests" value="writing" />
15 Writing
16 </label>
17</fieldset>
18
19<!-- Radio buttons — single choice -->
20<fieldset>
21 <legend>Preferred Contact Method</legend>
22
23 <label>
24 <input type="radio" name="contact" value="email" checked />
25 Email
26 </label>
27 <label>
28 <input type="radio" name="contact" value="phone" />
29 Phone
30 </label>
31 <label>
32 <input type="radio" name="contact" value="mail" />
33 Postal Mail
34 </label>
35</fieldset>
36
37<!-- Inline checkbox with explicit label -->
38<input type="checkbox" id="terms" name="terms" required />
39<label for="terms">
40 I agree to the <a href="/terms">Terms of Service</a>
41</label>

best practice

Wrap checkboxes and radios inside <label> elements for implicit association. This makes the entire label clickable and improves accessibility. For explicit association, use for matching id.

Live preview of checkboxes and radio groups:

preview
File Inputs

File inputs allow users to upload files from their local device. The <input type="file"> element opens the system file picker. Key attributes include accept for filtering file types, multiple for batch uploads, and capture for mobile camera access.

AttributeExampleDescription
acceptaccept="image/*,.pdf"MIME types or file extensions allowed
multiplemultipleAllow selecting multiple files
capturecapture="environment"Direct camera/mic access (mobile)
requiredrequiredForm cannot submit without a file
filesinput.filesFileList API (read-only, JavaScript)
file-inputs.html
HTML
1<!-- Single file upload — images only -->
2<label for="avatar">Profile Picture:</label>
3<input
4 type="file"
5 id="avatar"
6 name="avatar"
7 accept="image/png,image/jpeg,image/webp"
8/>
9
10<!-- Multiple file upload — PDFs and documents -->
11<label for="documents">Upload Documents:</label>
12<input
13 type="file"
14 id="documents"
15 name="documents"
16 multiple
17 accept=".pdf,.doc,.docx"
18/>
19
20<!-- Mobile camera capture -->
21<label for="photo">Take a Photo:</label>
22<input
23 type="file"
24 id="photo"
25 name="photo"
26 accept="image/*"
27 capture="environment"
28/>
29
30<!-- JavaScript: File API preview -->
31<script>
32 const input = document.getElementById('avatar');
33 const preview = document.getElementById('preview');
34
35 input.addEventListener('change', () => {
36 const file = input.files[0];
37 if (file) {
38 const reader = new FileReader();
39 reader.onload = (e) => {
40 preview.src = e.target.result;
41 };
42 reader.readAsDataURL(file);
43 }
44 });
45</script>
46<img id="preview" alt="Avatar preview" />

warning

File inputs are read-only for security reasons — you cannot set their value via JavaScript. The capture attribute works on mobile browsers and may trigger camera or microphone. Always validate file types and sizes server-side; client-side validation is easily bypassed.
Form Validation

HTML5 provides built-in constraint validation without JavaScript. Validation attributes include required, min/max, minlength/maxlength, pattern, step, and type-specific rules (email, url). The Constraint Validation API gives JavaScript access to validity states.

AttributeApplies ToDescription
requiredMost inputsField must have a value
min / maxnumber, date, rangeNumeric or date range constraints
minlength / maxlengthtext, textareaCharacter count limits
patterntext, tel, email, urlRegex pattern validation
stepnumber, date, rangeIncrement granularity
multipleemail, fileComma-separated values (email) or multiple files

The Constraint Validation API exposes the validityState object with boolean properties: valueMissing, typeMismatch, patternMismatch, tooLong, tooShort, rangeUnderflow, rangeOverflow, stepMismatch, badInput, and customError. Use setCustomValidity() to programmatically set validation messages.

form-validation.html
HTML
1<!-- Validation attributes in action -->
2<form id="signupForm" novalidate>
3 <label for="fullname">Full Name *</label>
4 <input
5 type="text"
6 id="fullname"
7 name="fullname"
8 required
9 minlength="2"
10 maxlength="100"
11 pattern="[A-Za-z\s]+"
12 title="Only letters and spaces allowed"
13 />
14
15 <label for="age">Age *</label>
16 <input
17 type="number"
18 id="age"
19 name="age"
20 required
21 min="18"
22 max="120"
23 step="1"
24 />
25
26 <label for="postcode">Postal Code</label>
27 <input
28 type="text"
29 id="postcode"
30 name="postcode"
31 pattern="[A-Z]{1,2}[0-9R][0-9A-Z]? [0-9][ABD-HJLNP-UW-Z]{2}"
32 title="UK postcode format e.g. SW1A 1AA"
33 />
34
35 <button type="submit">Submit</button>
36</form>
37
38<script>
39 const form = document.getElementById('signupForm');
40 const fullname = document.getElementById('fullname');
41
42 form.addEventListener('submit', (e) => {
43 e.preventDefault();
44
45 // Check validity
46 if (!fullname.checkValidity()) {
47 const state = fullname.validity;
48 if (state.valueMissing) {
49 fullname.setCustomValidity('Name is required');
50 } else if (state.patternMismatch) {
51 fullname.setCustomValidity('Only letters and spaces');
52 } else if (state.tooShort) {
53 fullname.setCustomValidity('Name must be at least 2 characters');
54 }
55 fullname.reportValidity();
56 return;
57 }
58
59 // Reset custom validity and submit
60 fullname.setCustomValidity('');
61 this.submit();
62 });
63</script>

info

Always call setCustomValidity('') to reset the custom error before re-checking validity. An empty string means no custom error. A non-empty string marks the field as invalid with that message.
Form Styling

CSS pseudo-classes enable styling form controls based on their validation state, without JavaScript. These classes are applied automatically by the browser as the user interacts with the form.

Pseudo-classApplied When
:validField passes all validation constraints
:invalidField fails any validation constraint
:requiredField has the required attribute
:optionalField does not have required
:in-rangeValue is within min/max bounds
:out-of-rangeValue is outside min/max bounds
:user-validUser has interacted and value is valid
:user-invalidUser has interacted and value is invalid
:placeholder-shownPlaceholder is currently visible
:focus-withinElement or any descendant has focus
form-styling.css
CSS
1/* Input base styles */
2input {
3 border: 1px solid #ccc;
4 padding: 8px 12px;
5 transition: border-color 0.2s, box-shadow 0.2s;
6}
7
8/* Valid state — green border */
9input:valid {
10 border-color: #22c55e;
11}
12
13/* Invalid state — red border */
14input:invalid {
15 border-color: #ef4444;
16}
17
18/* User-interacted invalid — show error styling */
19input:user-invalid {
20 border-color: #ef4444;
21 box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15);
22}
23
24/* User-interacted valid — confirm styling */
25input:user-valid {
26 border-color: #22c55e;
27 box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.15);
28}
29
30/* Required field indicator */
31input:required + label::after {
32 content: " *";
33 color: #ef4444;
34}
35
36/* Range slider states */
37input[type="range"]:in-range {
38 background: #22c55e;
39}
40
41input[type="range"]:out-of-range {
42 background: #ef4444;
43}
44
45/* Focus styles */
46input:focus {
47 outline: none;
48 border-color: #3b82f6;
49 box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
50}
51
52/* Disabled state */
53input:disabled {
54 opacity: 0.5;
55 cursor: not-allowed;
56 background: #f5f5f5;
57}

best practice

The :user-valid and :user-invalid pseudo-classes are newer CSS selectors that only apply after the user has interacted with the field. This prevents showing red error borders on empty required fields before the user has had a chance to type.

Live preview of form styling with validation states:

preview
Accessible Forms

Accessible forms ensure all users — including those using screen readers, keyboard navigation, or assistive technologies — can successfully interact with your interface. The foundation is proper labeling, logical grouping, and clear error communication.

TechniqueImplementationPurpose
Explicit label<label for="id">Associates label with input (clickable, screen reader)
aria-labelaria-label="Search"Invisible label when visual label is not possible
aria-labelledbyaria-labelledby="desc-id"References another element as label
fieldset + legend<fieldset><legend>Groups related controls with description
aria-invalidaria-invalid="true"Indicates invalid field to screen readers
aria-describedbyaria-describedby="error-msg"Links input to error or help text
aria-requiredaria-required="true"Announces field as required
role="alert"role="alert" on error divImmediately announces error to screen reader
accessible-forms.html
HTML
1<!-- Properly labeled form with error handling -->
2<form novalidate id="signupForm">
3 <fieldset>
4 <legend>Personal Information</legend>
5
6 <label for="fullname">Full Name</label>
7 <input
8 type="text"
9 id="fullname"
10 name="fullname"
11 required
12 aria-required="true"
13 aria-describedby="name-hint name-error"
14 />
15 <span id="name-hint" class="hint">
16 Enter your full name (minimum 2 characters)
17 </span>
18 <span id="name-error" class="error" role="alert" aria-live="assertive"></span>
19
20 <label for="email">Email Address</label>
21 <input
22 type="email"
23 id="email"
24 name="email"
25 required
26 aria-required="true"
27 aria-describedby="email-error"
28 />
29 <span id="email-error" class="error" role="alert" aria-live="polite"></span>
30 </fieldset>
31
32 <fieldset>
33 <legend>Payment Method</legend>
34
35 <label>
36 <input type="radio" name="payment" value="cc" checked />
37 Credit Card
38 </label>
39 <label>
40 <input type="radio" name="payment" value="paypal" />
41 PayPal
42 </label>
43 </fieldset>
44
45 <button type="submit">Sign Up</button>
46</form>
47
48<script>
49 const form = document.getElementById('signupForm');
50
51 form.addEventListener('submit', (e) => {
52 e.preventDefault();
53 let hasError = false;
54
55 const fields = [
56 { id: 'fullname', errorId: 'name-error', message: 'Full name is required' },
57 { id: 'email', errorId: 'email-error', message: 'Valid email is required' },
58 ];
59
60 fields.forEach(({ id, errorId, message }) => {
61 const input = document.getElementById(id);
62 const errorEl = document.getElementById(errorId);
63
64 if (!input.validity.valid) {
65 errorEl.textContent = message;
66 input.setAttribute('aria-invalid', 'true');
67 hasError = true;
68 } else {
69 errorEl.textContent = '';
70 input.removeAttribute('aria-invalid');
71 }
72 });
73
74 if (!hasError) {
75 alert('Form submitted successfully!');
76 }
77 });
78</script>

best practice

Always use native <label> elements over aria-label when possible. Native labels are clickable, work without JavaScript, and have broader screen reader support. Reserve aria-label for cases where no visible label is possible, such as icon-only buttons.
Best Practices

Security

Forms are a primary attack vector. Protecting against Cross-Site Request Forgery (CSRF) and Cross-Site Scripting (XSS) is essential for any production application.

ThreatRiskMitigation
CSRFAttacker submits form on user's behalfCSRF tokens, SameSite cookies, origin checks
XSSMalicious script injected into outputOutput encoding, Content-Security-Policy
SQL InjectionDatabase compromise via form inputParameterized queries, ORMs, input sanitization
File Upload AbuseMalicious files uploaded to serverValidate MIME, scan files, restrict extensions
ClickjackingUser tricked into clicking hidden formX-Frame-Options header, CSP frame-ancestors

UX Patterns

Progressive disclosure: show additional fields only when needed — hide shipping address until user selects a shipping method
Input masking: format phone numbers, credit cards, and dates as the user types using JavaScript or native inputmode
Inline validation: validate on blur (not keystroke) and show clear error messages near the relevant field
Autofocus: set autofocus on the first input of critical forms (login, search) but never on long forms where it scrolls the page
Button states: disable submit after first click to prevent double submission; show loading spinner during async operations
Keyboard navigation: ensure Tab order matches visual order; use tabindex=0 sparingly
Mobile-friendly: use the correct inputmode (numeric, email, url, tel) to trigger appropriate mobile keyboards
Save drafts: auto-save form progress to localStorage to prevent data loss on accidental navigation
🔥

pro tip

Use the inputmode attribute to control which mobile keyboard is shown: inputmode="numeric" for ZIP codes, inputmode="email" for email fields, inputmode="tel" for phone numbers. This is separate from type and provides finer keyboard control.

Live preview of a mobile-friendly, well-structured form incorporating accessibility and UX best practices:

preview
$Blueprint — Engineering Documentation·Section ID: HTML-03·Revision: 1.0