HTML input types: every type, what it shows and what it gives you

The type attribute decides three things at once: the control on screen, the keyboard a phone offers, and the checks the browser runs before a form is sent.

An HTML <input> becomes a different control depending on its type attribute. There are 22 types. text is the default, email checks for an address, date opens a date picker, checkbox draws a box to tick, and hidden draws nothing but still sends a value.

Try all of them below. Each row prints the field's live .value, which is exactly what your JavaScript reads and what the form sends.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Every HTML input type</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  p.tip { margin: 0 0 12px; font-size: 14px; color: #4b5563; }
  form { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; }
  .row { background: #fff; border-radius: 10px; padding: 10px 12px; box-shadow: 0 1px 3px rgba(0, 0, 0, .08); }
  .row label { display: block; font: 600 13px ui-monospace, Consolas, monospace; color: #1d4ed8; margin-bottom: 6px; }
  .row input:not([type=checkbox]):not([type=radio]):not([type=image]) { width: 100%; box-sizing: border-box; font-size: 16px; }
  output { display: block; margin-top: 6px; font: 12px ui-monospace, Consolas, monospace; color: #0f5132; word-break: break-all; }
</style>
</head>
<body>
<p class="tip">Use each field. The green line is its live <code>.value</code>.</p>
<form id="f" novalidate>  <!-- novalidate: submit even with invalid values, so every button can be tried -->
  <div class="row"><label for="t1">text</label><input id="t1" type="text" value="Hello"><output></output></div>
  <div class="row"><label for="t2">email</label><input id="t2" type="email" placeholder="you@example.com"><output></output></div>
  <div class="row"><label for="t3">password</label><input id="t3" type="password"><output></output></div>
  <div class="row"><label for="t4">number</label><input id="t4" type="number" min="0" max="10" step="1"><output></output></div>
  <div class="row"><label for="t5">tel</label><input id="t5" type="tel"><output></output></div>
  <div class="row"><label for="t6">url</label><input id="t6" type="url" placeholder="https://"><output></output></div>
  <div class="row"><label for="t7">search</label><input id="t7" type="search"><output></output></div>
  <div class="row"><label for="t8">date</label><input id="t8" type="date"><output></output></div>
  <div class="row"><label for="t9">time</label><input id="t9" type="time"><output></output></div>
  <div class="row"><label for="t10">datetime-local</label><input id="t10" type="datetime-local"><output></output></div>
  <div class="row"><label for="t11">month</label><input id="t11" type="month"><output></output></div>
  <div class="row"><label for="t12">week</label><input id="t12" type="week"><output></output></div>
  <div class="row"><label for="t13">color</label><input id="t13" type="color" value="#2563eb"><output></output></div>
  <div class="row"><label for="t14">range</label><input id="t14" type="range" min="0" max="100"><output></output></div>
  <div class="row"><label for="t15">checkbox</label><input id="t15" type="checkbox"><output></output></div>
  <div class="row"><label>radio</label>
    <input type="radio" name="size" value="S" aria-label="S"> S
    <input type="radio" name="size" value="M" aria-label="M"> M<output></output></div>
  <div class="row"><label for="t17">file</label><input id="t17" type="file"><output></output></div>
  <div class="row"><label>hidden (not drawn)</label><input type="hidden" name="source" value="gallery"><output></output></div>
  <div class="row"><label>submit / reset / button</label>
    <input type="submit" value="Submit"> <input type="reset" value="Reset"> <input type="button" value="Button"><output id="clicks">No clicks yet</output></div>
  <div class="row"><label>image (a picture that submits)</label>
    <input type="image" name="pic" alt="Go" width="96" height="32"
      src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='32'%3E%3Crect width='96' height='32' rx='6' fill='%230f766e'/%3E%3Ctext x='48' y='21' font-size='14' fill='white' text-anchor='middle' font-family='sans-serif'%3EGo%3C/text%3E%3C/svg%3E"><output>Clicking it submits the form</output></div>
  <div class="row"><label for="t21">type="banana" (unknown)</label><input id="t21" type="banana"><output id="unknown"></output></div>
</form>

<script>
  const form = document.getElementById('f');

  // Show one field's value in the <output> of its row
  function show(input) {
    const out = input.closest('.row').querySelector('output');
    if (input.type === 'radio') {
      const picked = form.querySelector('input[name=size]:checked');
      out.textContent = 'value: ' + JSON.stringify(picked ? picked.value : null);
    } else if (input.type === 'checkbox') {
      out.textContent = 'value: ' + JSON.stringify(input.value) + ', checked: ' + input.checked;
    } else if (!['submit', 'reset', 'button', 'image'].includes(input.type)) {
      out.textContent = 'value: ' + JSON.stringify(input.value);  // always a string
    }
  }

  form.querySelectorAll('input').forEach(show);
  document.getElementById('unknown').textContent += '  .type: "' + document.getElementById('t21').type + '"';

  form.addEventListener('input', (e) => show(e.target));
  form.addEventListener('change', (e) => show(e.target));

  // Buttons: nothing is sent anywhere, we only report what happened
  const clicks = document.getElementById('clicks');
  form.addEventListener('submit', (e) => {
    e.preventDefault();
    clicks.textContent = 'submit event from ' + (e.submitter ? e.submitter.type : '?');
  });
  form.addEventListener('reset', () => {
    clicks.textContent = 'reset: fields back to their starting values';
    setTimeout(() => form.querySelectorAll('input').forEach(show));  // values change after the event
  });
  form.querySelector('[type=button]').addEventListener('click', () => {
    clicks.textContent = 'button: does nothing unless your script does';
  });
</script>
</body>
</html>
Every input type, labelled. The green line is the field's .value as you use it. Nothing is sent anywhere.

Two things show up quickly. Every value is a string, even for number and range. And the last field, type="banana", is a normal text box: a type the browser does not know falls back to text.

All 22 input types at a glance

Type What it shows Value your script gets
text One-line text box What was typed
email Text box that checks the address shape "anna@example.com"
password Text box with the characters hidden What was typed
number Box that accepts numbers, often with arrows "42"
tel Plain text box, phone keypad on touch screens What was typed, any format
url Text box that checks for a full address "https://example.com"
search Text box styled for search; some browsers add a clear button What was typed
date Date picker "2026-09-26"
time Time picker "14:30"
datetime-local Date and time picker, no time zone "2026-09-26T14:30"
month Month and year picker "2026-09"
week Week number picker "2026-W39"
color Colour swatch that opens a picker "#2563eb"
range Slider "50" (the middle, by default)
checkbox Tick box "on" unless you set value; read .checked
radio One choice from a group sharing a name The value of the picked one
file File chooser button "C:\fakepath\photo.png"; use .files
hidden Nothing The value you set
submit Button that sends the form Its value is the button label
reset Button that restores starting values Not sent
button Button that does nothing by itself Not sent
image Picture that sends the form Click position as name.x and name.y

A few of these deserve a closer look. The file value is a fake path on purpose: the browser never tells a page where a file lives on the reader's disk.

Read the actual file through input.files. The hidden type has its own article, HTML form hidden field.

Which keyboard each type brings up on a phone

On a touch screen, the type also picks the on-screen keyboard. The browser only asks for a kind of keyboard. The exact layout comes from the phone and its keyboard app, so treat the sketch below as the idea, not a promise.

The type, or inputmode, asks for a kind of keyboard. The phone decides what it looks like.
The type, or inputmode, asks for a kind of keyboard. The phone decides what it looks like.
  • email: a letter keyboard with @ and . easy to reach.
  • tel: a phone keypad with digits and the symbols phone numbers use.
  • url: a letter keyboard with / and . close by.
  • number: a keyboard with digits.
  • search: a letter keyboard whose enter key may be labelled for search.
  • date, time, color, file: no keyboard; the browser shows its own picker.

When the data is digits but not a number, keep type="text" and add inputmode. It changes the keyboard and nothing else. The values are numeric, decimal, tel, email, url, search, text and none.

Built-in validation that comes with the type

Some types check the value before a form is sent. email wants something shaped like an address. url wants an absolute address with a scheme such as https://. number, date and range respect min, max and step.

Press Check below. Each field runs checkValidity() and prints which validity flag failed, next to the browser's own message from validationMessage.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Built-in validation by input type</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .field { background: #fff; border-radius: 10px; padding: 10px 12px; margin-bottom: 8px; box-shadow: 0 1px 3px rgba(0, 0, 0, .08); }
  label { display: block; font-size: 14px; font-weight: 600; margin-bottom: 4px; }
  label code { font-weight: 400; color: #1d4ed8; font-size: 12px; }
  input { width: 100%; box-sizing: border-box; font-size: 16px; padding: 6px 8px; }
  .msg { font-size: 13px; margin-top: 6px; min-height: 18px; }
  .ok { color: #0f5132; } .bad { color: #9a3412; }
  button { font-size: 16px; padding: 8px 18px; border: 0; border-radius: 8px; background: #1d4ed8; color: #fff; cursor: pointer; }
</style>
</head>
<body>
<div class="field">
  <label for="email">Email <code>type="email"</code></label>
  <input id="email" type="email" value="anna@">
  <div class="msg"></div>
</div>
<div class="field">
  <label for="url">Website <code>type="url"</code></label>
  <input id="url" type="url" value="example.com">
  <div class="msg"></div>
</div>
<div class="field">
  <label for="qty">Quantity <code>type="number" min="1" max="10" step="1"</code></label>
  <input id="qty" type="number" min="1" max="10" step="1" value="12">
  <div class="msg"></div>
</div>
<div class="field">
  <label for="code">Order code <code>pattern="[A-Z]{3}-[0-9]{3}"</code></label>
  <input id="code" type="text" pattern="[A-Z]{3}-[0-9]{3}" value="abc-123">
  <div class="msg"></div>
</div>
<button id="check" type="button">Check</button>

<script>
  // The validity flags the browser keeps on every input
  const FLAGS = ['valueMissing', 'typeMismatch', 'patternMismatch', 'rangeUnderflow',
                 'rangeOverflow', 'stepMismatch', 'badInput'];

  document.getElementById('check').addEventListener('click', () => {
    document.querySelectorAll('.field input').forEach((input) => {
      const msg = input.parentElement.querySelector('.msg');
      if (input.checkValidity()) {
        msg.className = 'msg ok';
        msg.textContent = 'Valid. value = ' + JSON.stringify(input.value);
      } else {
        const failed = FLAGS.filter((f) => input.validity[f]).join(', ');
        msg.className = 'msg bad';
        msg.textContent = failed + ': ' + input.validationMessage;  // the browser's own wording
      }
    });
  });
</script>
</body>
</html>
Four fields that start wrong. Check shows the failed validity flag and the browser's message. Fix a value and check again.

The flags are the useful part for scripts. typeMismatch means the value does not fit the type, patternMismatch means pattern failed.

rangeUnderflow and rangeOverflow come from min and max, and stepMismatch from step. badInput means the browser could not turn what was typed into a value at all.

The message text comes from the browser and follows the reader's language, so its wording differs between browsers.

Custom messages, styling invalid fields and the rest of the constraint rules are covered in HTML form validation. The check runs in the reader's browser only; a server must check again.

The attributes that matter per type

The type sets the control. A handful of attributes set its limits and help the browser fill it in.

Attribute Works on What it does
min, max number, range, date, time, month, week, datetime-local Lowest and highest allowed value
step the same types Allowed increments; step="any" allows any decimal
pattern text, search, tel, url, email, password A regular expression the whole value must match
minlength, maxlength the text-like types Length limits in characters
inputmode any text-like field Which on-screen keyboard to show
autocomplete most fields What the field holds, so the browser can fill it
required all except range, color, hidden and the buttons The field must not be empty

autocomplete takes names such as name, email, tel, postal-code, bday, username, current-password, new-password and one-time-code. The right one lets the browser or a password manager fill the field in one tap. To stop suggestions instead, see HTML input with no autocomplete.

Reading values in JavaScript

input.value is always a string, whatever the type. The date types also always use one fixed format, whatever the reader sees on screen.

The reader sees their local format. Your script gets one fixed string.
The reader sees their local format. Your script gets one fixed string.

Two helpers save conversions:

const qty = document.querySelector('#qty');
qty.valueAsNumber;   // 3, or NaN when empty or invalid

const day = document.querySelector('#day');
day.valueAsDate;     // a Date at midnight UTC, or null
day.value;           // "2026-09-26"

For a checkbox, read .checked, not .value. For a radio group, find the checked one with form.querySelector('input[name=size]:checked'). For a file, read input.files[0]. Setting a starting date has its own pitfalls, covered in HTML date input default value.

A finished example: a sign-up form

Here every field uses the type, inputmode and autocomplete that fit its data. Submit is intercepted, so the panel shows what the form would send instead of sending it.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sign-up form with the right input types</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  form, .result { max-width: 460px; margin: 0 auto 12px; background: #fff; border-radius: 12px; padding: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, .08); }
  h2 { margin: 0 0 12px; font-size: 18px; }
  label { display: block; font-size: 14px; font-weight: 600; margin: 10px 0 4px; }
  input:not([type=checkbox]) { width: 100%; box-sizing: border-box; font-size: 16px; padding: 8px 10px; border: 1px solid #cbd2dc; border-radius: 8px; }
  input:user-invalid { border-color: #c2410c; }  /* red only after the user has touched it */
  .hint { font-size: 12px; color: #6b7280; }
  .check { display: flex; gap: 8px; align-items: center; margin: 14px 0; font-size: 14px; }
  button { width: 100%; font-size: 16px; padding: 10px; border: 0; border-radius: 8px; background: #0f766e; color: #fff; cursor: pointer; }
  .result pre { margin: 0; font: 13px/1.5 ui-monospace, Consolas, monospace; white-space: pre-wrap; word-break: break-all; }
</style>
</head>
<body>
<form id="signup">
  <h2>Create an account</h2>

  <label for="name">Full name</label>
  <input id="name" name="name" type="text" autocomplete="name" required>

  <label for="email">Email</label>
  <input id="email" name="email" type="email" autocomplete="email" required>

  <label for="phone">Phone</label>
  <input id="phone" name="phone" type="tel" autocomplete="tel">
  <div class="hint">tel: phone keypad, any format accepted</div>

  <label for="zip">Postal code</label>
  <input id="zip" name="zip" type="text" inputmode="numeric" autocomplete="postal-code"
         pattern="[0-9]{5}" maxlength="5">
  <div class="hint">text + inputmode="numeric": digits keyboard, leading zeros kept</div>

  <label for="bday">Date of birth</label>
  <input id="bday" name="bday" type="date" autocomplete="bday" min="1900-01-01">

  <label for="pw">Password</label>
  <input id="pw" name="password" type="password" autocomplete="new-password" minlength="8" required>
  <div class="hint">new-password: lets a password manager offer a new one</div>

  <input type="hidden" name="plan" value="free">

  <label class="check"><input type="checkbox" name="terms" value="yes" required> I accept the terms</label>
  <button type="submit">Sign up</button>
</form>

<div class="result"><b>What the form would send</b><pre id="out">Fill in the form and press Sign up.</pre></div>

<script>
  const form = document.getElementById('signup');
  const out = document.getElementById('out');

  // The browser checks required, type, pattern, min and minlength before this runs.
  form.addEventListener('submit', (e) => {
    e.preventDefault();  // nothing leaves the page
    const lines = [];
    for (const [key, value] of new FormData(form)) {
      lines.push(key + ' = ' + JSON.stringify(key === 'password' ? '*'.repeat(value.length) : value));
    }
    out.textContent = lines.join('\n');
  });
</script>
</body>
</html>
Each field has the matching type, keyboard hint and autocomplete name. Nothing leaves the page.
  • Name: type="text" with autocomplete="name".
  • Email: type="email", so the address shape is checked and a phone offers @.
  • Phone: type="tel". It does no format check, because phone formats differ between countries.
  • Postal code: type="text" with inputmode="numeric" and pattern="[0-9]{5}". Leading zeros survive.
  • Date of birth: type="date" with autocomplete="bday".
  • Password: type="password" with autocomplete="new-password" and minlength="8".

The same form posting to a real server only needs an action, a method, and no preventDefault():

<form action="/signup" method="post">
  <input name="email" type="email" autocomplete="email" required>
  <button>Sign up</button>
</form>

What the browser then sends, and how to catch it in JavaScript, is in HTML form submit.

Picking a type for numbers that are not numbers

type="number" is for quantities: an amount, an age, a count. Postal codes, phone numbers, card numbers and IDs are codes written with digits. A number field treats them as quantities, and that breaks them in small ways.

A postal code in a number field can lose its leading zero. A text field with inputmode keeps it.
A postal code in a number field can lose its leading zero. A text field with inputmode keeps it.

Pressing the up arrow in a number field holding 02134 makes it 2135. Use type="text" with inputmode="numeric" for codes, and type="tel" for phone numbers.

When it does not work

What you see Cause Fix
A number field accepts the letter e 1e3 is a valid number (1000), so e is allowed Check the value in script, or use text with inputmode="numeric" and pattern
qty.value + 1 gives "31" .value is always a string Use valueAsNumber or Number()
A number field reads "" while showing text What was typed is not a valid number Check validity.badInput
The date shows 26/09/2026 but the value is 2026-09-26 The display follows the locale; the value is always yyyy-mm-dd Read and set value in yyyy-mm-dd
Setting value="09/26/2026" leaves the date empty The value must be yyyy-mm-dd Set "2026-09-26"
pattern is ignored pattern only applies to text, search, tel, url, email and password Use min, max and step on the other types
A postal code loses its leading zero It is in a type="number" field type="text" + inputmode="numeric"
A phone number with + or spaces is rejected It is in a type="number" field type="tel"
An unknown type shows a plain box The browser does not know it and uses text Check the spelling; input.type shows what the browser uses
type="url" rejects example.com It requires an absolute address Ask for https://, or use text with a placeholder

Input types are easiest to judge on the device people will use. A screenshot does not open a keyboard, and an emailed .html file may open as code on a phone.

To send the working version, paste the page into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written and its scripts run, so the people you send it to can tap each field on their own phone. If you change the code later, the same link shows the new version.

Questions people ask

How many input types are there in HTML?

The HTML standard defines 22 values for the type attribute: text, search, tel, url, email, password, date, month, week, time, datetime-local, number, range, color, checkbox, radio, file, submit, image, reset, button and hidden. Leaving type out, or using a value the browser does not know, gives a text field.

What happens if I use an input type the browser does not support?

The field behaves as type="text". Reading input.type in JavaScript returns "text", while getAttribute('type') still returns what you wrote. The page keeps working; the reader just gets a plain text box without the special control or checks.

Why does input.value return a string for type="number"?

value is always a string for every input type. Use input.valueAsNumber to get a number (NaN when the field is empty or invalid), or convert it yourself with Number(input.value).

Should I use type="number" for phone numbers, postal codes or card numbers?

No. Those are codes made of digits, not quantities. Use type="tel" for phone numbers, and type="text" with inputmode="numeric" for postal codes and card numbers. The phone keyboard is still digits, and leading zeros and spaces survive.

What is the difference between type and inputmode?

type changes the control and adds validation. inputmode only hints which on-screen keyboard to show and changes nothing else. That is why type="text" inputmode="numeric" gives a digit keyboard with none of the number field's behaviour.

Keep reading

Get an input value in JavaScript, for every kind of inputRead what a user typed or picked: .value, valueAsNumber, valueAsDate, checked, selects, radiRange slider in HTML: value, styling and two thumbsBuild an HTML range slider: show its value as it moves, style the track and thumb with CSS, HTML datalist: suggestions for an input, free text still allowedAdd suggestions to any text box with <datalist> and the list attribute. Live examples, datalThe HTML label: connect the words to the controlHow the HTML label tag works: for and id or wrapping, bigger checkbox click targets, screen The HTML text input: label it, read it, style itHow to make a text input box in HTML: the input tag with a label, value vs defaultValue, inpThe HTML textarea: size it, grow it, read itHow the HTML textarea works: rows and cols vs CSS, resize: none, auto height, placeholder, rHTML form validationValidate a form with required, type, pattern, min and max before writing any script. AttribuHTML input with no autocompleteautocomplete="off" works for ordinary fields and is ignored for passwords. The tokens browseHTML date input default valueA date input only accepts YYYY-MM-DD in its value attribute, whatever the box displays. How HTML form submit: what the browser sends, and how to take overWhat happens when an HTML form is submitted: GET vs POST, why unnamed fields vanish, the subHTML form hidden fieldA hidden input carries a value the reader does not see but the server receives. What it is fHTML to linkTurn HTML into a link in 5 steps: check the page, paste it into a NOS document, copy the sha