Allow Only Numbers in a Text Input With JavaScript or jQuery

Quick Summary

  • Give the field inputmode="numeric" for a number keypad on phones and pattern="[0-9]*" so the form isn't submitted with anything but digits.
  • An input event handler that removes every character that isn't a digit catches typing, pasting, dropped text and on-screen keyboards; the old keydown and keyCode check misses some of these.
  • Browser checks can be skipped, so validate the value on the server as well, for example with ctype_digit() in PHP.

To let a text field accept only numbers, give it inputmode="numeric" and pattern="[0-9]*", then remove anything that isn’t a digit in an input event handler. The input event fires after every change, whether the visitor types, pastes, drops text or uses a phone’s on-screen keyboard, so nothing gets past it. The browser can only help the visitor, though, so check the value on the server too.

Older snippets, including an earlier version of this post, block keys in a keydown handler that checks event.keyCode. That property is deprecated, and key filtering misses text that doesn’t arrive as a key press. The old code and its problems are at the end of this post.

The HTML: inputmode and pattern

HTML
<input type="text" inputmode="numeric" pattern="[0-9]*" id="rev_bid" name="rev_bid" value="18898" class="form-control">
  • inputmode="numeric" asks phones and tablets for a keyboard with the digits 0 to 9. It only changes the keyboard: as MDN points out, it doesn’t make the browser check the value. MDN’s compatibility data lists support from Chrome 66, Firefox 95, Safari 12.1 and Safari on iOS 12.2.
  • pattern="[0-9]*" stops the form from being submitted while the field contains anything other than digits. The pattern must match the whole value, not just part of it. In Chrome 153, checkValidity() returned false for 12a and 18,898, and true for 123.
  • An empty field is never checked against the pattern, so it counts as valid. Add required if the field can’t be left empty.
  • form-control is a Bootstrap class. Leave it out if you don’t use Bootstrap.

Remove anything that isn’t a digit

Put this in your JavaScript file, loaded after the field, for example in the footer or with the defer attribute:

JavaScript
const bidInput = document.getElementById('rev_bid');

bidInput.addEventListener('input', function () {
  const value = bidInput.value;
  const digits = value.replace(/\D/g, '');

  if (digits !== value) {
    // Put the cursor back after the same digit it followed before.
    const caret = value.slice(0, bidInput.selectionStart).replace(/\D/g, '').length;
    bidInput.value = digits;
    bidInput.setSelectionRange(caret, caret);
  }
});
  • \D matches any character that isn’t a digit from 0 to 9, and replace() with the g flag removes all of them.
  • The value is only rewritten when something was removed. Setting value moves the cursor to the end of the field, so the handler counts the digits in front of the cursor and puts it back there. Without those two lines, typing a letter in the middle of 123456 left the cursor at the end in Chrome.
  • No key is blocked, so Backspace, Delete, Tab, the arrow keys, the number pad and shortcuts like Ctrl+A keep working.

Tested in headless Chrome 153: typing 1a2b3 left 123, pasting 18,898 USD with Ctrl+V left 18898, and dropping the text a9b onto the field left only the 9. Text inserted without any key press, the way on-screen keyboards and input methods insert it, was cleaned the same way.

The same with jQuery

If jQuery is already on the page, for example on a WordPress site whose theme or plugins load it, the handler looks like this:

JavaScript
jQuery(function ($) {
    $('#rev_bid').on('input', function () {
        var value = this.value;
        var digits = value.replace(/\D/g, '');

        if (digits !== value) {
            var caret = value.slice(0, this.selectionStart).replace(/\D/g, '').length;
            this.value = digits;
            this.setSelectionRange(caret, caret);
        }
    });
});

jQuery(function ($) { … }) runs the code once the page is ready and passes jQuery in as $. That matters in WordPress, where jQuery runs in no-conflict mode and a bare $ is undefined (see fixing “$ is not a function” in WordPress). To cover several fields, give them a class such as digits-only and use $('.digits-only') as the selector. For fields added to the page later, bind the handler with $(document).on('input', '.digits-only', …) instead.

Why not cancel beforeinput instead?

The beforeinput event fires before the change is made, and calling event.preventDefault() in it stops the change. In Chrome 153 that kept typed letters out. But MDN notes that the event isn’t always cancelable: changes made by autocomplete, spell-check corrections, password managers or an input method may go through anyway, and MDN recommends handling the input event as well. Since you need the input handler in any case, it’s enough on its own.

Why not use type=”number”?

<input type="number"> is made for quantities. It comes with min, max and step, and the up and down arrow keys change the value. It isn’t a digits-only field, though. In Chrome 153:

  • It accepted 1e5, which is a valid number (100000). The minus sign, plus sign, dot and e can all be typed.
  • When the field held something it couldn’t read as a number, such as 1e, its value was an empty string.
  • Setting its value to 18,898 left it empty, because a number field can’t hold a thousands separator.

Use type="number" when the arrows and the min, max and step checks help, and a text field with inputmode="numeric" when you need digits only.

Check the value on the server

Anything that runs in the browser can be skipped: a visitor can turn off JavaScript or send the form data without your page. So check the value again where the form is processed. In PHP:

PHP
$bid = $_POST['rev_bid'] ?? '';

if ( ! is_string( $bid ) || ! ctype_digit( $bid ) ) {
	// Reject the form: the field is empty or contains something other than 0-9.
}

ctype_digit() returns true only when every character is a digit, and false for an empty string. The is_string() check rejects an array, which someone can send by naming the field rev_bid[]; passing a non-string to ctype_digit() has been deprecated since PHP 8.1. Run with PHP 8.2, the check accepted 18898 and 007 and rejected 18,898, 12a, -5, " 12", an empty value and an array. Cast the value with (int) after the check if you need it as a number.

What if the number needs a thousands separator?

The pattern and the handler above allow digits only, so 18,898 becomes 18898. If visitors should be able to type the comma, add it to both: pattern="[0-9,]*" in the HTML and value.replace(/[^\d,]/g, '') in the handler. Then remove the commas on the server with str_replace( ',', '', $bid ) before you check the value. To display a stored number with separators, see number formatting in JavaScript.

The old keydown version

An earlier version of this post used this jQuery code, which blocks keys by their key code:

JavaScript
$(document).ready(function() {
    $("#rev_bid").keydown(function(event) {
        // Allow only backspace and delete
        if ( event.keyCode == 46 || event.keyCode == 8 ) {
            // let it happen, don't do anything
        }
        else {
            // Ensure that it is a number and stop the keypress
            if (event.keyCode < 48 || event.keyCode > 57 ) {
                event.preventDefault();
            }
        }
    });
});

It allows Backspace (8), Delete (46) and the number keys above the letters (48 to 57), and cancels every other key. Tested with jQuery 3.7.1 in Chrome 153:

  • It kept typed letters out, but it also blocked Tab, the arrow keys, the number pad digits and Ctrl+V, since V is just another key.
  • Text that doesn’t come from a key press got through: dropped text, and text inserted the way on-screen keyboards and input methods insert it. Pasting from the right-click menu doesn’t press a key either.
  • With WordPress’s copy of jQuery, which runs in no-conflict mode, $(document).ready stopped with TypeError: $ is not a function, so nothing was blocked at all.

keyCode itself is deprecated; MDN recommends event.key or event.code instead. The .keydown(handler) shorthand has been deprecated since jQuery 3.3 as well, in favor of .on('keydown', handler). On Android, Chrome’s on-screen keyboards report most keys with the code 229, as described in Chromium issue 118639, so a key code check can’t tell a digit from a letter there. Replacing the code with the input handler above fixes all of this.

Share on X (opens in a new tab) Share on LinkedIn (opens in a new tab) Share on Facebook (opens in a new tab)

ChatGPT (opens in a new tab) Claude (opens in a new tab) Gemini (opens in a new tab) Perplexity (opens in a new tab) Grok (opens in a new tab)

More Insights

See More