Have you ever wondered how to implement that smooth UX flow you can find on most credit card forms? Did you wonder how to separate the credit card number into groups of four, expiration date with `MM/YY` format in the input field, etc?
Well, neither did I until I had to implement it for the first time. 😅
As always, there is no need to reinvent the wheel, and there are plenty of good libraries that solve this problem if you’re doing your own app/hobby project, most of these will do. But if you’re implementing someone else’s design, you’ll often find that it’s hard to perfectly fit these finished solutions into an existing system.
We’ll go with the middle road: we’ll use Eric Rasmussen’s (a known form expert) card utils and add a couple of extra features so it all perfectly fits with the rest of our UI.
So let’s get started with the credit card number formatting.
export function formatCreditCardNumber(value: string) {
if (!value) {
return value;
}
const clearValue = clearNumber(value);
let nextValue;
nextValue = `${clearValue.slice(0, 4)} ${clearValue.slice(
4,
8
)} ${clearValue.slice(8, 12)} ${clearValue.slice(12, 19)}`;
return nextValue.trim();
}
function clearNumber(value = '') {
return value.replace(/\D+/g, '');
}
We utilize the function like this:
{({ touched, errors, setFieldValue }) => {
return (
<Form
className={`${
className ? className : ''
} flex flex-col space-y-4 text-black lg:space-y-6`}
>
<FieldInput
maxLength={20}
name="cardNumber"
type="text"
inputMode="numeric"
placeholder="0000 0000 0000 0000"
label={t('dashboard:account.cards.card-number')}
id="cardNumber"
onKeyPress={event => {
checkIsNumber(event);
}}
onChange={event => {
const formatted =
(isNaN(
Number(
event.target.value.replace(
/\s/g,
''
)
)
) && // edge-case of c/p-ing strings into cc number field
'0') ||
formatCreditCardNumber(
event.target.value
);
setFieldValue('cardNumber', formatted);
}}
// …
/>
A keen eye will notice a couple of things:
- In this example, we’re using Formik, but the function is general enough that it can be applied to most form libraries
- checkIsNumber and isNaN are there to prevent users from inputting anything into the CC number filed
const checkIsNumber = (e: React.KeyboardEvent<HTMLInputElement>) => {
const pattern = /^[0-9]$/;
if (!pattern.test(e.key)) {
e.preventDefault();
e.stopPropagation();
return false;
}
return true;
};
Eric elegantly solved this issue with pattern attribute on the input tag.
pattern="[\d| ]{16,22}"
You’ll forgive a JavaScript programmer for once again proving true the saying “To a man with a hammer, everything looks like a nail” 😅
- You can apply some additional formatting based on the Credit card type (Visa, MasterCard, Amex…) for which Eric used an external library but I would advise using some variation of my handy utility function for checking the card type. this goes for the CVC number as well to determine if a 4-digit number is needed (for American Express) or a 3-digit one (for everyone else).
Finally, we format the expiration date with
export function formatExpirationDate(value: string) {
const clearValue = clearNumber(value);
if (clearValue.length >= 3) {
return `${
+clearValue.slice(0, 2) > 12 ? 12 : clearValue.slice(0, 2)
}/${clearValue.slice(2, 4)}`;
}
return clearValue || '';
}
To achieve the final result:
That’s all, folks! If you want to find out some minor improvements to this code & nuances – get in touch and let me know!