Choosing inputmode and enterkeyhint
Which keyboard should appear for a card number, a price, a verification code, a phone number or a URL — and what should the Enter key say and do on each field? Two attributes decide it. inputmode chooses the virtual keyboard without changing the field’s validation semantics, and enterkeyhint labels the Enter key (“next”, “done”, “go”, “send”). Chosen well, they prevent whole categories of validation errors before any rule runs; chosen badly, they force users to hunt for a decimal point or submit a half-finished form. This recipe maps common fields to the right combination of type, inputmode and enterkeyhint, explains when type should do the job instead, and wires Enter so its behaviour matches its label, while the form still validates through reportValidity() from the Constraint Validation API.
When to Use inputmode Instead of type
type and inputmode overlap, and the choice matters for validation:
- Use
typewhen you want the browser’s semantics and validation:type="email"checks email syntax,type="url"checks for a scheme,type="tel"gives a phone keypad and autofill without validating (numbering plans vary),type="number"for true quantities with spinners andmin/max/step. - Use
inputmodeontype="text"when you want a keyboard without the semantics: identifiers made of digits (card numbers, codes, account numbers, postcodes in some countries), decimals that should not becomenumbervalues, and URLs typed without a scheme.
The rule of thumb: if leading zeros, formatting or length matter more than numeric value, it is text with inputmode="numeric", not type="number". The reasoning for card fields is in payment card validation, and the broader mobile context in mobile form validation UX.
Minimal Working Keyboard Setup
<form id="pay" novalidate>
<label for="email">Email address</label>
<input id="email" name="email" type="email" autocomplete="email"
autocapitalize="none" spellcheck="false" enterkeyhint="next" required>
<label for="amount">Amount (£)</label>
<input id="amount" name="amount" type="text" inputmode="decimal"
pattern="\d+([.,]\d{1,2})?" enterkeyhint="next" required>
<label for="card">Card number</label>
<input id="card" name="cardnumber" type="text" inputmode="numeric"
autocomplete="cc-number" enterkeyhint="next" required>
<label for="code">Security code</label>
<input id="code" name="cvc" type="text" inputmode="numeric"
autocomplete="cc-csc" maxlength="4" enterkeyhint="go" required>
<button type="submit">Pay</button>
</form>
const form = document.querySelector<HTMLFormElement>("#pay")!;
// Make Enter do what its label says: "next" moves on, the last field's "go" submits.
form.addEventListener("keydown", (event) => {
if (event.key !== "Enter" || event.isComposing) return; // don't hijack IME confirmation
const target = event.target as HTMLInputElement;
const hint = target.getAttribute("enterkeyhint");
if (hint === "next") {
event.preventDefault();
const fields = [...form.querySelectorAll<HTMLInputElement>("input:not([disabled]):not([type=hidden])")];
fields[fields.indexOf(target) + 1]?.focus();
}
// "go", "done", "send": let Enter submit through the normal handler below.
});
form.addEventListener("submit", (event) => {
// Normalise decimal commas typed on European keyboards before validating.
const amount = form.querySelector<HTMLInputElement>("#amount")!;
amount.value = amount.value.replace(",", ".");
if (!form.reportValidity()) event.preventDefault();
});
The event.isComposing check matters for users typing with an input method editor (Chinese, Japanese, Korean): their Enter press confirms a composition and must not move focus or submit. The decimal normalisation matters because inputmode="decimal" shows the locale’s decimal separator, which is a comma for much of Europe; the validation rule has to accept what the keyboard offers.
inputmode and enterkeyhint Reference
| Attribute | Value | Keyboard / label | Typical fields |
|---|---|---|---|
inputmode |
numeric |
Digits only | Card numbers, codes, account numbers |
inputmode |
decimal |
Digits plus locale decimal separator | Prices, weights, measurements |
inputmode |
tel |
Phone keypad with + * # |
Phone numbers (or use type="tel") |
inputmode |
email |
@ and . prominent |
When type="email" is not wanted |
inputmode |
url |
/ and .com |
Website fields typed without a scheme |
inputmode |
search |
Enter labelled for search | Search boxes |
inputmode |
none |
No virtual keyboard | Fields with a custom on-screen picker only |
enterkeyhint |
next / previous |
“Next” / “Previous” | Every field except the last |
enterkeyhint |
done / go / send / search |
Action label | Final field of a form |
inputmode="none" deserves caution: it hides the keyboard entirely, which is only appropriate when the component provides its own complete input method, such as a custom date picker. Used carelessly it makes a field impossible to type into.
Verification Steps
import { test, expect } from "@playwright/test";
test("Enter on a 'next' field moves focus instead of submitting", async ({ page }) => {
let submitted = false;
page.on("request", (r) => { if (r.method() === "POST") submitted = true; });
await page.goto("/pay");
await page.getByLabel("Email address").fill("ada@example.com");
await page.getByLabel("Email address").press("Enter");
await expect(page.getByLabel("Amount (£)")).toBeFocused();
expect(submitted).toBe(false);
});
Edge Cases and Failure Modes
type="number" side effects. Number inputs change value when a user scrolls over them with a mouse, reject non-numeric characters silently (the value becomes "" and badInput is set), and strip leading zeros from what you read. For identifiers these are bugs; for quantities, add min, max, step and handle badInput explicitly.
inputmode does not validate. A numeric keyboard can still produce letters through paste, hardware keyboards and autofill. Keep a pattern or script rule and normalise input — spaces in card numbers, commas in decimals — before validating.
Enter labelled “next” but submitting. If you set enterkeyhint="next" without handling Enter, the browser still submits the form, contradicting the label. Label and behaviour must be changed together.
Search fields inside forms. A search box embedded in a larger form (for example, an address lookup) should use enterkeyhint="search" and handle Enter itself, so pressing it runs the lookup instead of submitting the whole form half-completed.
Keyboards that ignore the hint. Third-party keyboards on Android sometimes ignore inputmode or enterkeyhint. Treat both as enhancements: the field must still accept and validate whatever the user manages to type.
Hardware keyboards on tablets. inputmode has no effect when a physical keyboard is attached, so validation must never assume a restricted character set.
Text Handling Attributes That Pair With inputmode
The keyboard is only half of the typing experience on mobile; the other half is what the keyboard does to text as it is typed. Three attributes control it, and each prevents a class of validation errors. autocapitalize decides whether the first letter of each sentence or word is capitalised — essential to turn off for emails, usernames, codes and URLs, where “Ada@example.com” or “Promo2026” would fail case-sensitive checks or look wrong. autocorrect (honoured by Safari) and spellcheck stop the keyboard replacing identifiers with dictionary words — a username “adalove” silently becoming “adorable” is a real report, not a joke. And autocomplete connects the field to saved data, which on mobile is the single largest error-prevention feature: an autofilled email or address has no typos. The combination for an identifier field is therefore inputmode for the keyboard, autocapitalize="none" autocorrect="off" spellcheck="false" for the text, and the correct autocomplete token, as described in autocomplete attribute and validation. Name fields are the exception: leave capitalisation on for them (autocapitalize="words"), because users expect their names to start with capitals.
Handling Enter in Multi-Line Fields and Last Fields
Textareas are different: Enter inserts a new line, and hijacking it to move focus breaks writing. Leave Enter alone in textareas and rely on the submit button (or a modifier shortcut submitted through requestSubmit(), as in using requestSubmit() to trigger validation). For the last single-line field, enterkeyhint="done" or go tells users that Enter will finish the form; the submit handler then validates everything, and if a field higher up is invalid, reportValidity() scrolls to it and focuses it — which on mobile also reopens the keyboard for that field, exactly where the user needs it. Test that journey on a real phone: Enter on the last field with an earlier error should land the user on the earlier field with its message visible above the keyboard, as covered in validating forms with the virtual keyboard open.
Frequently Asked Questions
What is the difference between inputmode and type?
type sets the input's semantics and built-in validation, such as email syntax or numeric values. inputmode only chooses the virtual keyboard, so a text input with inputmode="numeric" shows a digit keypad without number semantics.
Should card numbers use type number?
No. Use type="text" with inputmode="numeric" and autocomplete="cc-number". Number inputs drop leading zeros, react to scrolling and show spinners, none of which suit identifiers.
What does enterkeyhint do?
It changes the label on the virtual keyboard's Enter key to next, done, go, send, search or previous. It does not change behaviour, so handle Enter in script to match the label.
How do I accept decimal commas on mobile?
inputmode="decimal" shows the locale's decimal separator. Accept both comma and point in your pattern or rule, and normalise the value before validating and submitting.
Related Guides
- Mobile Form Validation UX — the wider mobile context.
- Number Input min, max and step Validation — when type number is right.
- Detecting Card Brand and Formatting Input — numeric identifiers with formatting.
- Autocomplete Attribute and Validation — the tokens that pair with these keyboards.