0Support

HTML input playground

Explore all 22 input types, their attributes and native validation. Compare values, states and form data without sending anything to a server.

Read the guide

Ask AI

HTML input playground

Response generated with openai/gpt-5.4-nano. AI can make mistakes. Always review the result.

Use fictional data. Nothing entered here is sent or included in the shared URL.

Interact with the field or check its validity.

No entries for this control.
HTML
<form id="input-demo" class="input-demo" method="post" action="#">
  <label for="input-demo-field">Example field (Required)</label>
  <p id="input-demo-help">Use fictional data. Nothing entered here is sent or included in the shared URL.</p>
  <input
    id="input-demo-field"
    type="email"
    name="example"
    aria-describedby="input-demo-help input-demo-error"
    placeholder="you@example.com"
    autocomplete="email"
    required
    autocapitalize="off"
    spellcheck="false"
  >
  <p id="input-demo-error" data-input-error role="status"></p>
  <div class="demo-actions">
    <button type="button" data-check>Check validity</button>
    <button type="submit">Test local submission</button>
    <button type="reset">Reset example</button>
  </div>
  <p data-input-status role="status">Interact with the field or check its validity.</p>
  <pre data-input-data aria-label="Form data">No entries for this control.</pre>
</form>
JS
// Local demonstration: replace with your server endpoint in a real form.
const form = document.getElementById("input-demo");
const field = document.getElementById("input-demo-field");

// Named controls may shadow form methods (for example name="submit").
const query = (selector) => Element.prototype.querySelector.call(form, selector);

const on = (type, listener, capture = false) => EventTarget.prototype.addEventListener.call(form, type, listener, capture);

const error = query('[data-input-error]');
const status = query('[data-input-status]');
const output = query('[data-input-data]');

field.setCustomValidity("");

function showValidity() {
  const invalid = field.willValidate && !field.validity.valid;
  field.setAttribute('aria-invalid', String(invalid));
  error.textContent = invalid ? field.validationMessage : '';
  status.textContent = !field.willValidate ? "This control does not participate in validation (willValidate = false)." : invalid ? "The value does not satisfy the constraints." : "Valid under the current constraints.";
}

field.addEventListener('input', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
});

field.addEventListener('blur', showValidity);

on('invalid', showValidity, true);

query('[data-check]').addEventListener('click', () => {
  HTMLFormElement.prototype.reportValidity.call(form);
  showValidity();
});

on('submit', (event) => {
  event.preventDefault();
  const data = new FormData(form, event.submitter);
  output.textContent = JSON.stringify(Array.from(data, ([name, value]) => [name,
    value instanceof File ? { name: value.name, size: value.size, type: value.type } : value
  ]), null, 2);
  status.textContent = "Local submission simulated. No request was made.";
});

on('reset', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
  output.textContent = "No entries for this control.";
  status.textContent = "Interact with the field or check its validity.";
});

Educational guide to input

Choose an input type, validate and submit data

Choose a type for the data you need and understand how interface, constraints and submission fit together. A convenient picker does not guarantee valid data on your server.

Back to playground

Mental model

One element: semantics, constraints, validity and data.

  1. 1. Data and label

    label + type + visible instructions

  2. 2. Constraints

    required · pattern · bounds · custom rules

  3. 3. Validation and submission

    ValidityState → FormData → server validation

label → type → validity → FormDataOne element: semantics, constraints, validity and data.

type defines semantics and interface, attributes add constraints, the browser determines validity and name identifies the data participating in the form. The HTML value attribute establishes the initial value; the value property reflects the current value. checked has the same distinction with defaultChecked.

All 22 input types

Complete catalog of current types. datetime is obsolete; datetime-local is current.

type="text"
Single-line text. The default when type is absent or unrecognized. Use text and keyboard hints for identifiers, postal codes or card numbers.
type="tel"
Telephone number. May offer a phone keypad; it enforces no universal format. Add instructions and pattern if your domain requires a specific format.
type="url"
Absolute URL. Reports typeMismatch for invalid URL syntax; it does not check that the resource exists or is safe.
type="email"
Email address. Checks basic syntax, not existence or ownership. multiple allows a comma-separated list.
type="password"
Visually obscured text. Supports minlength, maxlength and pattern. Does not encrypt, prevent pasting or replace HTTPS; use appropriate autocomplete.
type="number"
Numeric quantity. min/max set the range and step sets granularity. Supports neither pattern nor maxlength; do not use it to preserve leading zeros.
type="range"
Approximate numeric value. Defaults to min=0, max=100 and step=1; the browser clamps and adjusts values. required and readonly do not apply.
type="date"
Calendar date. value uses YYYY-MM-DD even when the picker is localized. min/max are dates and step is measured in days.
type="month"
Year and month in YYYY-MM format; step is measured in months. Limited support: it may fall back to text without enforcing temporal bounds.
type="week"
ISO year and week in YYYY-Www format; step is measured in weeks. Limited support: it may fall back to text. An ISO week can belong to a different calendar year.
type="time"
Time without a zone, HH:mm or HH:mm:ss. step is measured in seconds and defaults to 60. Submitted format does not depend on the interface’s 12/24-hour clock.
type="datetime-local"
Local date and time: YYYY-MM-DDTHH:mm. Contains no time zone and does not convert to UTC; store the zone separately when your product needs it.
type="color"
Color picker. #RRGGBB is the interoperable base. alpha and colorspace extend input with partial support; recognizing type=color does not prove support for them.
type="checkbox"
Independent choice. Contributes name=value only when checked and enabled; without value it submits on. required requires that particular box to be checked.
type="radio"
One option per group sharing a name and form owner. required requires a group selection; use fieldset and legend for the common question.
type="file"
Local file selection. accept guides the picker, multiple allows several files and capture may suggest a camera. value cannot preload a file.
type="hidden"
Data without an interface and excluded from validation. Its value is still visible and editable with browser tools; it is never a secret or authorization proof.
type="button"
Button with no default action; value supplies its visible name. Needs JavaScript to perform an action. The button element allows richer content.
type="submit"
Submits the form and triggers validation unless novalidate/formnovalidate applies. Only the activated submitter contributes its own name=value.
type="reset"
Restores initial values and checked states; does not necessarily empty fields or clear setCustomValidity errors. May cause accidental data loss.
type="image"
Graphical submit button. Requires src and descriptive alt. Contributes click coordinates name.x/name.y, rather than the value of a regular submit button.

Attributes and constraints

An attribute can exist in the DOM without affecting the current type.

required requires content on supporting types. On checkbox it requires that box to be checked; on radio it requires a group selection. It does not affect hidden, range, color or buttons. An empty optional field is usually valid: pattern and minlength do not replace required.

pattern applies to text, search, tel, url, email and password, matches the entire value and uses regular expression syntax with the v flag, without /…/ delimiters. Explain the format in visible text; title alone is insufficient. An invalid pattern is ignored, not enforced more strictly.

minlength/maxlength apply to text types and count UTF-16 code units, not necessarily perceived characters. Their constraints depend on user editing; setting value through JavaScript does not reproduce tooShort/tooLong. maxlength can prevent further typing instead of producing a visible error.

min/max/step apply to number, range and temporal types. step is calculated from min, the initial value attribute or a type-specific base; checking divisibility from zero is insufficient. number/range default to step=1; date uses days, month months, week weeks and time/datetime-local seconds (60 by default). any removes the step constraint.

autocomplete identifies the expected data to help autofill on desktop and mobile. For example, email, username and one-time-code describe different data. It is a hint, not a validator; password managers may ignore off.

inputmode and enterkeyhint suggest the virtual keyboard layout and Enter key label, commonly on phones and tablets. They do not change physical keyboards or validate values; enterkeyhint does not implement actions by itself.

placeholder does not replace label. size affects presentation on supporting types; use CSS for responsive layout. list connects datalist suggestions without restricting allowed values.

Native and custom validation

Separate technical validity from the right moment to show an error.

validity exposes valueMissing, typeMismatch, patternMismatch, tooShort, tooLong, rangeUnderflow, rangeOverflow, stepMismatch, badInput and customError; valid summarizes the result. willValidate indicates participation. validationMessage depends on the browser and its language. checkValidity() returns a boolean and fires invalid; reportValidity() also requests native error UI.

setCustomValidity("message") maintains customError while its message is non-empty. Correct the rule and call setCustomValidity("") to clear it; changing value or resetting a form does not clear it automatically. The playground lets you set a demonstration error and remove it by emptying its control.

Do not display an error when an empty required field first loads. Put instructions above the field, communicate errors after leaving it or attempting submission and clear visual feedback when editing resumes. :user-invalid allows styling after interaction; combine styles with text, aria-invalid and associated descriptions.

What is submitted and excluded

Validity and inclusion in FormData are separate concerns.

name is the data key: an unnamed control contributes no entry. disabled is excluded from both FormData and validation; readonly can receive focus and is submitted, but is not validated. Unchecked checkbox/radio inputs are excluded. An enabled, named hidden input is included even though it is invisible. FormData does not check validity itself. Avoid names such as submit or reset that can shadow form methods.

requestSubmit() reproduces submission with validation and a submit event; form.submit() bypasses them. novalidate on form and formnovalidate on a submitter bypass interactive validation on submission, but reportValidity() still checks it. new FormData(form, submitter) includes the activated button; type=image contributes coordinates. This demo cancels submit and displays data locally.

form can associate a control with a form by id even outside it. action, method and enctype belong on the form; formaction, formmethod, formenctype, formtarget and formnovalidate override submission on submit/image. Use GET for idempotent queries and POST for sensitive data or mutations; files need multipart/form-data in native HTML submission. Always validate and authorize on the server.

Files and native pickers

Selection is neither uploading nor content validation.

files contains File objects. accept filters or guides selection but does not validate actual type, size or contents; check these on the server. capture is a capture hint for supporting devices. You cannot set a local file path through value or learn the real system path. The demo shows names, types and sizes without reading contents.

Native, reusable examples

Independent scenes outside the playground. Checking and submitting act locally. Copied HTML uses POST and its JS cancels the demonstration submission.

Required email

Use fictional data. Nothing entered here is sent or included in the shared URL.

Interact with the field or check its validity.

No entries for this control.
Edit the field and use Check validity or Test local submission; inspect the message and form data.
Required email
<form id="input-guide-example-email" class="input-demo" method="post" action="#">
  <label for="input-guide-example-email-field">Example field (Required)</label>
  <p id="input-guide-example-email-help">Use fictional data. Nothing entered here is sent or included in the shared URL.</p>
  <input
    id="input-guide-example-email-field"
    type="email"
    name="example"
    aria-describedby="input-guide-example-email-help input-guide-example-email-error"
    placeholder="you@example.com"
    autocomplete="email"
    required
    autocapitalize="off"
    spellcheck="false"
  >
  <p id="input-guide-example-email-error" data-input-error role="status"></p>
  <div class="demo-actions">
    <button type="button" data-check>Check validity</button>
    <button type="submit">Test local submission</button>
    <button type="reset">Reset example</button>
  </div>
  <p data-input-status role="status">Interact with the field or check its validity.</p>
  <pre data-input-data aria-label="Form data">No entries for this control.</pre>
</form>
// Local demonstration: replace with your server endpoint in a real form.
const form = document.getElementById("input-guide-example-email");
const field = document.getElementById("input-guide-example-email-field");

// Named controls may shadow form methods (for example name="submit").
const query = (selector) => Element.prototype.querySelector.call(form, selector);

const on = (type, listener, capture = false) => EventTarget.prototype.addEventListener.call(form, type, listener, capture);

const error = query('[data-input-error]');
const status = query('[data-input-status]');
const output = query('[data-input-data]');

field.setCustomValidity("");

function showValidity() {
  const invalid = field.willValidate && !field.validity.valid;
  field.setAttribute('aria-invalid', String(invalid));
  error.textContent = invalid ? field.validationMessage : '';
  status.textContent = !field.willValidate ? "This control does not participate in validation (willValidate = false)." : invalid ? "The value does not satisfy the constraints." : "Valid under the current constraints.";
}

field.addEventListener('input', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
});

field.addEventListener('blur', showValidity);

on('invalid', showValidity, true);

query('[data-check]').addEventListener('click', () => {
  HTMLFormElement.prototype.reportValidity.call(form);
  showValidity();
});

on('submit', (event) => {
  event.preventDefault();
  const data = new FormData(form, event.submitter);
  output.textContent = JSON.stringify(Array.from(data, ([name, value]) => [name,
    value instanceof File ? { name: value.name, size: value.size, type: value.type } : value
  ]), null, 2);
  status.textContent = "Local submission simulated. No request was made.";
});

on('reset', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
  output.textContent = "No entries for this control.";
  status.textContent = "Interact with the field or check its validity.";
});

Code preserving leading zeros

Exactly four digits (0–9); leading zeros are allowed. Applied constraints: pattern=[0-9]{4}; minlength=4; maxlength=4.

Interact with the field or check its validity.

No entries for this control.
Edit the field and use Check validity or Test local submission; inspect the message and form data.
Code preserving leading zeros
<form id="input-guide-example-code" class="input-demo" method="post" action="#">
  <label for="input-guide-example-code-field">Example field (Required)</label>
  <p id="input-guide-example-code-help">Exactly four digits (0–9); leading zeros are allowed. Applied constraints: pattern=[0-9]{4}; minlength=4; maxlength=4.</p>
  <input
    id="input-guide-example-code-field"
    type="text"
    name="example"
    aria-describedby="input-guide-example-code-help input-guide-example-code-error"
    placeholder="0123"
    pattern="[0-9]{4}"
    minlength="4"
    maxlength="4"
    autocomplete="one-time-code"
    inputmode="numeric"
    required
  >
  <p id="input-guide-example-code-error" data-input-error role="status"></p>
  <div class="demo-actions">
    <button type="button" data-check>Check validity</button>
    <button type="submit">Test local submission</button>
    <button type="reset">Reset example</button>
  </div>
  <p data-input-status role="status">Interact with the field or check its validity.</p>
  <pre data-input-data aria-label="Form data">No entries for this control.</pre>
</form>
// Local demonstration: replace with your server endpoint in a real form.
const form = document.getElementById("input-guide-example-code");
const field = document.getElementById("input-guide-example-code-field");

// Named controls may shadow form methods (for example name="submit").
const query = (selector) => Element.prototype.querySelector.call(form, selector);

const on = (type, listener, capture = false) => EventTarget.prototype.addEventListener.call(form, type, listener, capture);

const error = query('[data-input-error]');
const status = query('[data-input-status]');
const output = query('[data-input-data]');

field.setCustomValidity("");

function showValidity() {
  const invalid = field.willValidate && !field.validity.valid;
  field.setAttribute('aria-invalid', String(invalid));
  error.textContent = invalid ? field.validationMessage : '';
  status.textContent = !field.willValidate ? "This control does not participate in validation (willValidate = false)." : invalid ? "The value does not satisfy the constraints." : "Valid under the current constraints.";
}

field.addEventListener('input', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
});

field.addEventListener('blur', showValidity);

on('invalid', showValidity, true);

query('[data-check]').addEventListener('click', () => {
  HTMLFormElement.prototype.reportValidity.call(form);
  showValidity();
});

on('submit', (event) => {
  event.preventDefault();
  const data = new FormData(form, event.submitter);
  output.textContent = JSON.stringify(Array.from(data, ([name, value]) => [name,
    value instanceof File ? { name: value.name, size: value.size, type: value.type } : value
  ]), null, 2);
  status.textContent = "Local submission simulated. No request was made.";
});

on('reset', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
  output.textContent = "No entries for this control.";
  status.textContent = "Interact with the field or check its validity.";
});

Quantity and increments

Use fictional data. Nothing entered here is sent or included in the shared URL. Applied constraints: min=2; max=12; step=2.

Interact with the field or check its validity.

No entries for this control.
Edit the field and use Check validity or Test local submission; inspect the message and form data.
Quantity and increments
<form id="input-guide-example-number" class="input-demo" method="post" action="#">
  <label for="input-guide-example-number-field">Example field (Required)</label>
  <p id="input-guide-example-number-help">Use fictional data. Nothing entered here is sent or included in the shared URL. Applied constraints: min=2; max=12; step=2.</p>
  <input
    id="input-guide-example-number-field"
    type="number"
    name="example"
    aria-describedby="input-guide-example-number-help input-guide-example-number-error"
    value="4"
    min="2"
    max="12"
    step="2"
    required
  >
  <p id="input-guide-example-number-error" data-input-error role="status"></p>
  <div class="demo-actions">
    <button type="button" data-check>Check validity</button>
    <button type="submit">Test local submission</button>
    <button type="reset">Reset example</button>
  </div>
  <p data-input-status role="status">Interact with the field or check its validity.</p>
  <pre data-input-data aria-label="Form data">No entries for this control.</pre>
</form>
// Local demonstration: replace with your server endpoint in a real form.
const form = document.getElementById("input-guide-example-number");
const field = document.getElementById("input-guide-example-number-field");

// Named controls may shadow form methods (for example name="submit").
const query = (selector) => Element.prototype.querySelector.call(form, selector);

const on = (type, listener, capture = false) => EventTarget.prototype.addEventListener.call(form, type, listener, capture);

const error = query('[data-input-error]');
const status = query('[data-input-status]');
const output = query('[data-input-data]');

field.setCustomValidity("");

function showValidity() {
  const invalid = field.willValidate && !field.validity.valid;
  field.setAttribute('aria-invalid', String(invalid));
  error.textContent = invalid ? field.validationMessage : '';
  status.textContent = !field.willValidate ? "This control does not participate in validation (willValidate = false)." : invalid ? "The value does not satisfy the constraints." : "Valid under the current constraints.";
}

field.addEventListener('input', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
});

field.addEventListener('blur', showValidity);

on('invalid', showValidity, true);

query('[data-check]').addEventListener('click', () => {
  HTMLFormElement.prototype.reportValidity.call(form);
  showValidity();
});

on('submit', (event) => {
  event.preventDefault();
  const data = new FormData(form, event.submitter);
  output.textContent = JSON.stringify(Array.from(data, ([name, value]) => [name,
    value instanceof File ? { name: value.name, size: value.size, type: value.type } : value
  ]), null, 2);
  status.textContent = "Local submission simulated. No request was made.";
});

on('reset', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
  output.textContent = "No entries for this control.";
  status.textContent = "Interact with the field or check its validity.";
});

Choose one option

Primary option

Use fictional data. Nothing entered here is sent or included in the shared URL.

Interact with the field or check its validity.

No entries for this control.
Edit the field and use Check validity or Test local submission; inspect the message and form data.
Choose one option
<form id="input-guide-example-radio" class="input-demo" method="post" action="#">
  <fieldset>
    <legend>Primary option</legend>
    <label for="input-guide-example-radio-field">First option (Required)</label>
    <p id="input-guide-example-radio-help">Use fictional data. Nothing entered here is sent or included in the shared URL.</p>
    <input
      id="input-guide-example-radio-field"
      type="radio"
      name="example"
      aria-describedby="input-guide-example-radio-help input-guide-example-radio-error"
      value="accepted"
      required
    >
    <label for="input-guide-example-radio-second">Second option</label>
    <input id="input-guide-example-radio-second" type="radio" name="example" value="alternative" aria-describedby="input-guide-example-radio-help input-guide-example-radio-error" required>
  </fieldset>
  <p id="input-guide-example-radio-error" data-input-error role="status"></p>
  <div class="demo-actions">
    <button type="button" data-check>Check validity</button>
    <button type="submit">Test local submission</button>
    <button type="reset">Reset example</button>
  </div>
  <p data-input-status role="status">Interact with the field or check its validity.</p>
  <pre data-input-data aria-label="Form data">No entries for this control.</pre>
</form>
// Local demonstration: replace with your server endpoint in a real form.
const form = document.getElementById("input-guide-example-radio");
const field = document.getElementById("input-guide-example-radio-field");

// Named controls may shadow form methods (for example name="submit").
const query = (selector) => Element.prototype.querySelector.call(form, selector);

const on = (type, listener, capture = false) => EventTarget.prototype.addEventListener.call(form, type, listener, capture);

const error = query('[data-input-error]');
const status = query('[data-input-status]');
const output = query('[data-input-data]');

field.setCustomValidity("");

function showValidity() {
  const invalid = field.willValidate && !field.validity.valid;
  field.setAttribute('aria-invalid', String(invalid));
  error.textContent = invalid ? field.validationMessage : '';
  status.textContent = !field.willValidate ? "This control does not participate in validation (willValidate = false)." : invalid ? "The value does not satisfy the constraints." : "Valid under the current constraints.";
}

field.addEventListener('input', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
});

field.addEventListener('blur', showValidity);

on('invalid', showValidity, true);

query('[data-check]').addEventListener('click', () => {
  HTMLFormElement.prototype.reportValidity.call(form);
  showValidity();
});

on('submit', (event) => {
  event.preventDefault();
  const data = new FormData(form, event.submitter);
  output.textContent = JSON.stringify(Array.from(data, ([name, value]) => [name,
    value instanceof File ? { name: value.name, size: value.size, type: value.type } : value
  ]), null, 2);
  status.textContent = "Local submission simulated. No request was made.";
});

on('reset', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
  output.textContent = "No entries for this control.";
  status.textContent = "Interact with the field or check its validity.";
});

Multiple file selection

Use fictional data. Nothing entered here is sent or included in the shared URL. Applied constraints: accept=image/*,.pdf.

Interact with the field or check its validity.

No entries for this control.
Edit the field and use Check validity or Test local submission; inspect the message and form data.
Multiple file selection
<form id="input-guide-example-files" class="input-demo" method="post" action="#" enctype="multipart/form-data">
  <label for="input-guide-example-files-field">Example field (Required)</label>
  <p id="input-guide-example-files-help">Use fictional data. Nothing entered here is sent or included in the shared URL. Applied constraints: accept=image/*,.pdf.</p>
  <input
    id="input-guide-example-files-field"
    type="file"
    name="example"
    aria-describedby="input-guide-example-files-help input-guide-example-files-error"
    accept="image/*,.pdf"
    required
    multiple
  >
  <p id="input-guide-example-files-error" data-input-error role="status"></p>
  <div class="demo-actions">
    <button type="button" data-check>Check validity</button>
    <button type="submit">Test local submission</button>
    <button type="reset">Reset example</button>
  </div>
  <p data-input-status role="status">Interact with the field or check its validity.</p>
  <pre data-input-data aria-label="Form data">No entries for this control.</pre>
</form>
// Local demonstration: replace with your server endpoint in a real form.
const form = document.getElementById("input-guide-example-files");
const field = document.getElementById("input-guide-example-files-field");

// Named controls may shadow form methods (for example name="submit").
const query = (selector) => Element.prototype.querySelector.call(form, selector);

const on = (type, listener, capture = false) => EventTarget.prototype.addEventListener.call(form, type, listener, capture);

const error = query('[data-input-error]');
const status = query('[data-input-status]');
const output = query('[data-input-data]');

field.setCustomValidity("");

function showValidity() {
  const invalid = field.willValidate && !field.validity.valid;
  field.setAttribute('aria-invalid', String(invalid));
  error.textContent = invalid ? field.validationMessage : '';
  status.textContent = !field.willValidate ? "This control does not participate in validation (willValidate = false)." : invalid ? "The value does not satisfy the constraints." : "Valid under the current constraints.";
}

field.addEventListener('input', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
});

field.addEventListener('blur', showValidity);

on('invalid', showValidity, true);

query('[data-check]').addEventListener('click', () => {
  HTMLFormElement.prototype.reportValidity.call(form);
  showValidity();
});

on('submit', (event) => {
  event.preventDefault();
  const data = new FormData(form, event.submitter);
  output.textContent = JSON.stringify(Array.from(data, ([name, value]) => [name,
    value instanceof File ? { name: value.name, size: value.size, type: value.type } : value
  ]), null, 2);
  status.textContent = "Local submission simulated. No request was made.";
});

on('reset', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
  output.textContent = "No entries for this control.";
  status.textContent = "Interact with the field or check its validity.";
});

A readonly value is included

The value cannot be edited, but it appears in the form data.

Interact with the field or check its validity.

No entries for this control.
Edit the field and use Check validity or Test local submission; inspect the message and form data.
A readonly value is included
<form id="input-guide-example-readonly" class="input-demo" method="post" action="#">
  <label for="input-guide-example-readonly-field">Example field</label>
  <p id="input-guide-example-readonly-help">The value cannot be edited, but it appears in the form data.</p>
  <input
    id="input-guide-example-readonly-field"
    type="text"
    name="example"
    aria-describedby="input-guide-example-readonly-help input-guide-example-readonly-error"
    value="BL-2026"
    readonly
  >
  <p id="input-guide-example-readonly-error" data-input-error role="status"></p>
  <div class="demo-actions">
    <button type="button" data-check>Check validity</button>
    <button type="submit">Test local submission</button>
    <button type="reset">Reset example</button>
  </div>
  <p data-input-status role="status">Interact with the field or check its validity.</p>
  <pre data-input-data aria-label="Form data">No entries for this control.</pre>
</form>
// Local demonstration: replace with your server endpoint in a real form.
const form = document.getElementById("input-guide-example-readonly");
const field = document.getElementById("input-guide-example-readonly-field");

// Named controls may shadow form methods (for example name="submit").
const query = (selector) => Element.prototype.querySelector.call(form, selector);

const on = (type, listener, capture = false) => EventTarget.prototype.addEventListener.call(form, type, listener, capture);

const error = query('[data-input-error]');
const status = query('[data-input-status]');
const output = query('[data-input-data]');

field.setCustomValidity("");

function showValidity() {
  const invalid = field.willValidate && !field.validity.valid;
  field.setAttribute('aria-invalid', String(invalid));
  error.textContent = invalid ? field.validationMessage : '';
  status.textContent = !field.willValidate ? "This control does not participate in validation (willValidate = false)." : invalid ? "The value does not satisfy the constraints." : "Valid under the current constraints.";
}

field.addEventListener('input', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
});

field.addEventListener('blur', showValidity);

on('invalid', showValidity, true);

query('[data-check]').addEventListener('click', () => {
  HTMLFormElement.prototype.reportValidity.call(form);
  showValidity();
});

on('submit', (event) => {
  event.preventDefault();
  const data = new FormData(form, event.submitter);
  output.textContent = JSON.stringify(Array.from(data, ([name, value]) => [name,
    value instanceof File ? { name: value.name, size: value.size, type: value.type } : value
  ]), null, 2);
  status.textContent = "Local submission simulated. No request was made.";
});

on('reset', () => {
  field.removeAttribute('aria-invalid');
  error.textContent = '';
  output.textContent = "No entries for this control.";
  status.textContent = "Interact with the field or check its validity.";
});

Accessibility and common mistakes

Native semantics help; the complete flow still needs verification.

Associate every visible field with a label; use fieldset/legend for groups. Explain formats and requirements, connect hints and errors with aria-describedby and preserve visible focus, keyboard access and zoom. Do not hide an operable input with display:none to draw a fake control. Buttons need accessible names and image needs alt. Do not block pasting or useful autofill.

Avoid number for postal codes, universal telephone formats, expecting email to prove existence, treating hidden as secret or trusting accept as protection. Browser validation improves the experience; the server protects the data. Test without JavaScript, with assistive technologies and on target devices too.

Continue with datalist and suggestions

Support and compatibility

Check each feature and retain a usable alternative.

General availability of input and the Constraint Validation API does not certify every type or interface. month and week have limited support and may fall back to text: provide a visible format, a usable alternative and server validation. Checking that input.type retains a value only tests recognition; it does not certify a picker’s quality or accessibility.

Basic color uses #RRGGBB. alpha and colorspace enable wider color formats in supporting engines and may be ignored elsewhere. This guide does not guarantee identical camera, color or date pickers across systems. datetime-local has no time-zone information.