← All writing

29 October 2026 · FormsJavaScriptPayments

A card field that respects the caret

Formatting a card number as you type is easy until someone edits the middle of it. Here’s how to keep the caret where the person put it, plus the Luhn check.

Card number fields are deceptively simple. Everyone wants the spaces (“4111 1111 1111 1111”) because they make the number readable. The trouble starts when you reformat on every keystroke: the browser puts the caret at the end, and anyone who goes back to fix a digit in the middle gets thrown to the end of the field.

Try it
Network: —Waiting for digits

Type a number, then click into the middle and change a digit. The caret stays put.

Count digits, not characters

The trick is to remember the caret’s position in terms of digits, not characters, because the spaces move around as you type.

  1. Before formatting, count how many digits sit to the left of the caret.
  2. Strip everything that isn’t a digit, cut to the network’s length, and regroup.
  3. Walk through the new value and place the caret right after the same number of digits.
const digitsBefore = input.value.slice(0, input.selectionStart).replace(/\D/g, '').length;
input.value = group(digits, sizes);
let pos = 0, seen = 0;
while (pos < input.value.length && seen < digitsBefore) {
  if (/\d/.test(input.value[pos])) seen++;
  pos++;
}
input.setSelectionRange(pos, pos);

Group by network

Not every card is 4-4-4-4. American Express numbers are grouped 4-6-5 and are 15 digits long. The first digits tell you the network, and the network tells you the grouping and the maximum length, so detect the network before you format.

The Luhn check

Card numbers carry a check digit. Starting from the rightmost digit, double every second digit, subtract 9 from any result over 9, and add everything up. A valid number gives a total divisible by 10. It catches most single-digit typos and swapped neighbours before the customer ever presses pay. The field above highlights the digits that get doubled.

Small things that help

  • inputmode="numeric" brings up the number pad on phones without the quirks of type="number".
  • autocomplete="cc-number" lets the browser and password managers fill it.
  • Say what’s wrong in words (“Luhn check fails”), not only with a red border.
↑↓ move↵ openesc close