If you’re using the modern WooCommerce checkout built with Gutenberg blocks, you may have noticed that customizing form fields is a bit trickier than it was with the classic shortcode-based checkout. One common request is to apply a phone number input mask, such as formatting input as (123) 456-7890 while the user types.
In this article, we’ll walk you through how to implement a working phone number mask in the WooCommerce Blocks checkout that works for both guest and logged-in users, and avoids common pitfalls like values being reset or the script not initializing properly.
⚠️ The Problem
When using WooCommerce Blocks, fields like the billing phone number are rendered and managed by React. This means:
- Custom JavaScript that manipulates the field might run before the field exists.
- Even if you apply a mask, WooCommerce might overwrite your changes when the form updates.
- Using libraries like
jquery.mask
can result in fields not behaving correctly or resetting on blur.
This makes it challenging to provide a consistent user experience for formatting phone numbers.
✅ The Goal
We want the #billing-phone
field to:
- Automatically format input into the format
(123) 456-7890
- Work for both logged-in users and guests
- Be persistent even after WooCommerce re-renders the form
- Avoid triggering infinite loops or console errors
📄 The Solution
We use a small JavaScript snippet embedded via PHP in your theme or custom plugin. This snippet:
- Waits for the
#billing-phone
field to appear - Adds a custom input mask using native JavaScript
- Formats the phone number while typing
- Dispatches
input
andchange
events so React respects the updated value - Prevents infinite loops by checking if the value is already formatted
Here’s the full working code:
add_action( 'wp_enqueue_scripts', 'mask_checkout_phone_number' );
function mask_checkout_phone_number() {
if ( is_checkout() ) {
wp_add_inline_script(
'wp-hooks',
'document.addEventListener("DOMContentLoaded", function () {
function formatPhoneNumber(value) {
const digits = value.replace(/\D/g, "").substring(0, 10);
const parts = [];
if (digits.length > 0) parts.push("(" + digits.substring(0, 3));
if (digits.length >= 4) parts.push(") " + digits.substring(3, 6));
if (digits.length >= 7) parts.push("-" + digits.substring(6, 10));
return parts.join("");
}
function updateReactInputValue(el, newValue) {
if (el.value === newValue) return;
el.value = newValue;
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
}
function attachMask(input) {
if (!input || input.dataset.masked) return;
input.addEventListener("input", function (e) {
const formatted = formatPhoneNumber(e.target.value);
updateReactInputValue(e.target, formatted);
});
input.dataset.masked = "true";
}
function observePhoneInput() {
const input = document.querySelector("#billing-phone");
if (input) attachMask(input);
}
observePhoneInput();
const observer = new MutationObserver(() => observePhoneInput());
observer.observe(document.body, { childList: true, subtree: true });
});'
);
}
}
🎮 Why This Works
- The
MutationObserver
makes sure the script adapts to React rerenders - Formatting happens only when needed (avoids
RangeError
) - Events are bubbled properly so WooCommerce Blocks accepts the value
📊 Result
Now, when your customers enter a phone number at checkout, they see it automatically formatted as (123) 456-7890
. This works smoothly across all user types and device types.
📣 Final Notes
- Make sure the
id
of your phone field isbilling-phone
. This is standard for WooCommerce Blocks. - If you’re using additional validation or localization, you can enhance the script further (e.g., international formats).
- This approach keeps your checkout JavaScript-light and React-compatible.
Let us know in the comments if you’d like to see versions for other fields or country-specific formats!
Leave a Reply