JSFiddle - React, Tailwind, and code Playground

by Sergey Aleshin

HTML

<input type="text" id="card" placeholder="**** **** **** ****">

CSS

input {
	text-align: left;
}

JavaScript

var caretpos = {
	start: 0,
	end: 0
};
var currentCaretPos = 0;
const isDeleteMod = () => ( caretpos.end < caretpos.start );

document.getElementById("card").onkeydown = (e) => {
	caretpos.start = e.currentTarget.selectionStart;
}

document.getElementById("card").onkeyup = (e) => {
	if (!e.currentTarget.value.replace(/\s*/g, '').length) return;
/* 	if (!e.currentTarget.value.replace(/\sg, '').match(/^[0-9]*$/g)) {
		e.currentTarget.value = e.currentTarget.value.replace(/[^0-9]+/g, '')
	} */
	caretpos.end = e.currentTarget.selectionEnd;
	currentCaretPos = caretpos.end;
	
	let spacesIndex = [];
	for (let i = 0, pos = 0; i < e.currentTarget.value.replace(/\s*/g, '').length; i++, pos ++) {
  	if (pos >= 4) {
    	pos = 0;
      spacesIndex.push(i);
    }
  }
  let j = 0,
  		outStringArr = [];
  spacesIndex.map(i => {
    outStringArr.push(e.currentTarget.value.replace(/\s*/g, '').slice(j, i));
    j = i;
  });

  e.currentTarget.value = outStringArr.join(' ') + (outStringArr.join(' ').length > 0 ? ' ' : '') + e.currentTarget.value.replace(/\s*/g, '').slice(
    outStringArr.join('').length, e.currentTarget.value.replace(/\s*/g, '').length
  );
	if (isDeleteMod() || currentCaretPos < e.currentTarget.value.replace(/\s*/g, '').length) {
		e.currentTarget.selectionStart = currentCaretPos;
		e.currentTarget.selectionEnd = currentCaretPos;
	}
	
	/* console.log(e.currentTarget.value.replace(/\sg, '')) */
};