HTML Reference · CLN v2.2 · Feb 2026
HTML: Forms
= structure, elements, attributes, validation — encoded in CLN ! most gotchas live in default behaviours — type, for, method # #html #forms #a11y #validation
HTML decoded as CLN. Operators encode semantic role — not decoration. = definition · ! gotcha · → consequence · ? self-test. Hover operators for context · click spoilers to reveal.

01/ Form Anatomy Structure
// ##form-root · The <form> element
=
=Definition
⎘Copy
<form> — container that groups controls and defines how data is sent
.
.Attribute
⎘Copy
action="/endpoint" — URL to send data to · omit → sends to current URL
. method="get" | "post"
= get — appends data to URL as query string · bookmarkable · visible
= post — sends data in request body · not visible in URL · use for sensitive data
!
!Gotcha
⎘Copy
✔Noted
default is "get" — passwords in URL is a critical security risk
. enctype — encoding for post forms
= application/x-www-form-urlencoded — default · key=value pairs
= multipart/form-data — required for file uploads
! forget multipart/form-data on file upload → server receives empty file field
. novalidate — disables browser's built-in validation · use when writing custom JS validation
Preview
HTML Source
<form action="/login" method="post">
  <label for="email">Email</label>
  <input type="email" id="email" name="email"
         placeholder="you@example.com" required>

  <label for="pass">Password</label>
  <input type="password" id="pass" name="pass"
         placeholder="••••••••" required>

  <button type="submit">Sign in</button>
</form>
? What happens if you submit a <form> with no method attribute? Sends as GET — data appended to URL. Fine for search, dangerous for passwords or tokens. Always declare method explicitly.

02/ Form Elements Reference
Input controls
= <input> — single-line input, self-closing
! no closing tag — </input> is invalid
= <textarea> — multi-line · has closing tag
. rows / cols — hint for initial size (CSS overrides)
! whitespace between tags appears as pre-filled text Write <textarea></textarea> on one line. A newline inside creates a newline in the field value.
= <select> + <option> — dropdown
. multiple — allows multi-select (hold Ctrl/Cmd)
. <optgroup label=""> — groups options visually
Structure & Labels
= <label> — visible name for a control
. for="id" — links label to input by ID
! for must exactly match input's id Mismatch → click on label doesn't focus the field. Common bug: label for="Email" but id="email" (case mismatch).
+ clicking label focuses/checks associated control — larger click target
= <fieldset> — groups related controls
. <legend> — title for the fieldset · read by screen readers
+ use for radio groups and checkboxes — semantic grouping
= <button> — clickable button
! default type="submit" inside a form — always declare type explicitly

03/ Input Types Reference
// ##input-types · type= values and their behaviour
= type controls: validation · keyboard on mobile · browser UI
Text input types
.text — default · no validation
.email — validates @ format · shows email keyboard on mobile
.password — masks input · disables copy on some browsers
.search — adds clear button · semantically a search field
.url — validates URL format
.tel — numeric keyboard on mobile · no format validation Intentional — phone formats vary too much globally. Use pattern= if you need validation.
Numeric & Date types
.number — spinner UI · min / max / step
!accepts "e", "." — not a pure integer input Use pattern="\d*" + inputmode="numeric" for integer-only fields. type="number" allows scientific notation like 1e5.
.range — slider · min / max / step
.date — date picker · value format: YYYY-MM-DD
.datetime-local — date + time · no timezone
.month · week · time — partial date pickers
Choice & Action types
.checkbox — boolean · checked sets default state
!unchecked checkbox sends nothing — not "false" To detect "unchecked": add a hidden input with same name and value "false" before the checkbox. The checkbox overwrites it only when checked.
.radio — one of group · group by same name
.file — file picker · accept=".pdf,.jpg"
.multiple — allows multi-file selection
.hidden — invisible · sends value with form
.color — color picker · value: #rrggbb
.submit · reset · button — use <button> instead
? What does type="tel" actually validate? Nothing. It only triggers a numeric keyboard on mobile. Add pattern="[0-9+\-\s()]+" if you need format validation. This is a deliberate spec decision — phone formats vary too much globally for one validation rule.
? A user unchecks a checkbox and submits. What does the server receive? Nothing. The field is absent from the payload entirely — not "false", not "0", just absent. Server must treat absence as false. Alternatively: hidden input trick — place <input type="hidden" name="subscribe" value="0"> before the checkbox. Checkbox value "1" overwrites it when checked.

04/ Key Attributes Validation & UX
// ##validation · Validation
.required — field must have value before submit
.minlength / maxlength — character count
.min / max — numeric or date bounds
.pattern="regex" — custom regex validation
*pattern applies to whole value — no need for ^ and $
!pattern on type="email" coexists — both must pass
.step="0.01" — valid increment for number/range/date
→browser shows error tooltip on submit · see »Accessibility for custom messages
// ##ux · UX Attributes
.placeholder="hint" — ghost text · disappears on focus
!placeholder ≠ label — never replace labels with placeholders Once user starts typing, label is gone. User forgets what the field was. Screen readers may ignore placeholder. Always use a visible <label>.
.autofocus — focus this field on page load
!only one per page · jumps viewport on mobile
.autocomplete="on|off|email|new-password|..."
*new-password prevents browser suggesting old passwords
.disabled — greyed out · not sent with form
.readonly — not editable · IS sent with form
~disabled vs readonly: one submits the value, one doesn't Use readonly when the value matters for the submission (e.g. auto-filled user ID). Use disabled when the control is irrelevant to current context.

05/ Gotchas Common Mistakes
// ##gotchas · The bugs that bite everyone once
!
!Gotcha
⎘Copy
✔Noted
Button inside form submits by default
= <button> without type → browser assumes type="submit"
→ clicking "Cancel" button inside a form reloads the page Fix: <button type="button" onclick="...">Cancel</button>. The type="button" attribute explicitly prevents form submission.
! Enter key submits form
= pressing Enter in any text input submits the form — always
→ first <button> or type="submit" input in DOM gets triggered If you have a search field that shouldn't submit a payment form, use form= attribute to bind inputs to specific form IDs, or use novalidate + JS submit handler.
! name= is required for submission
= input without name attribute → value not included in form data
* id= is for CSS/JS · name= is for form submission — they serve different purposes
! they're often set to the same value — but both must be present id="email" alone → label for="email" works, JS getElementById works, but server receives nothing. name="email" alone → server receives data, but label click and JS getElementById don't work.
! Radio groups need identical name=
= all radio buttons in a group must share the same name
→ different name → can select multiple "exclusive" options simultaneously
? An input has id="search" but no name. User fills it and submits. What does the server receive? Nothing from that field. The id attribute is only for DOM access (CSS, JS, label linking). The name attribute determines what key appears in the submitted form data. No name → no data sent.

06/ Accessibility a11y
// ##a11y-required · Required patterns
.every <input> must have an associated <label>
=via for="id", wrapping, or aria-label
.radio/checkbox groups → <fieldset> + <legend>
.error messages → aria-describedby="error-id"
*screen reader announces input name + error when focused
.required → aria-required="true" (redundant but explicit)
.invalid → aria-invalid="true" on the input
// ##a11y-anti · Anti-patterns
-placeholder-only labels — invisible after typing
-aria-label on a <div onclick> instead of real <button>
→div isn't keyboard-focusable by default — Tab skips it
-custom error messages without aria-live
→error appears visually but screen reader doesn't announce it Fix: wrap error container in <div aria-live="polite"> or <div role="alert">. Any content change inside is announced automatically.
-colour-only error indication — fails colour blindness check
-autocomplete="off" on login forms — breaks password managers
? What is the minimum accessible pattern for a text input with a visible error? <label for="email">Email</label> <input id="email" name="email" aria-describedby="email-err" aria-invalid="true"> <span id="email-err" role="alert">Enter a valid email</span> The aria-describedby links the error to the input. The role="alert" announces it on content change. The aria-invalid signals to screen readers the field is in error state.
// ##a11y-summary · Accessibility checklist
.every control has a visible label ✔
.radio/checkbox groups use fieldset + legend ✔
.errors linked via aria-describedby + aria-invalid ✔
.error container has role="alert" or aria-live ✔
.all interactive elements reachable by Tab ✔
.colour is not the only error indicator ✔
→ passes WCAG 2.1 AA for forms · test with VoiceOver / NVDA