Skip to content

Instantly share code, notes, and snippets.

@delaneyj
Created January 24, 2026 22:15
Show Gist options
  • Select an option

  • Save delaneyj/dbf5769add0fb36c1f56aa954862ea34 to your computer and use it in GitHub Desktop.

Select an option

Save delaneyj/dbf5769add0fb36c1f56aa954862ea34 to your computer and use it in GitHub Desktop.
class OtpInput extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.values = [];
}
connectedCallback() {
this.length = Number(this.getAttribute("length") || 6);
this.values = Array(this.length).fill("");
this.render();
}
render() {
const inputs = Array.from({ length: this.length }, () => {
return '<input type="number" inputmode="numeric" />';
}).join("");
this.shadowRoot.innerHTML = `
<div>
<div id="inputs">${inputs}</div>
<p>Entered Code: <span id="code"></span></p>
</div>
`;
this.inputs = Array.from(this.shadowRoot.querySelectorAll("input"));
const container = this.shadowRoot.getElementById("inputs");
container.addEventListener("keydown", (event) => this.handleKeydown(event));
container.addEventListener("paste", (event) => this.handlePaste(event));
}
updateCode() {
this.code = this.values.join("");
this.shadowRoot.getElementById("code").textContent = this.code;
}
handleKeydown(event) {
const input = event.target;
if (!input || input.tagName !== "INPUT" || event.ctrlKey || event.metaKey || event.altKey) return;
const isDigit = /^[0-9]$/.test(event.key);
if (!isDigit && event.key !== "Backspace" && event.key !== "ArrowLeft" && event.key !== "ArrowRight" && event.key !== "Tab") return;
const index = this.inputs.indexOf(input);
if (event.key === "ArrowLeft") {
event.preventDefault();
const prevInput = input.previousElementSibling;
if (prevInput && prevInput.tagName === "INPUT") prevInput.focus();
} else if (event.key === "ArrowRight") {
event.preventDefault();
const nextInput = input.nextElementSibling;
if (nextInput && nextInput.tagName === "INPUT") nextInput.focus();
} else if (event.key === "Tab") {
return;
} else if (event.key === "Backspace") {
event.preventDefault();
input.value = "";
this.values[index] = "";
const prevInput = input.previousElementSibling;
if (prevInput && prevInput.tagName === "INPUT") prevInput.focus();
this.updateCode();
} else if (isDigit) {
event.preventDefault();
input.value = event.key;
this.values[index] = event.key;
const nextInput = input.nextElementSibling;
if (nextInput && nextInput.tagName === "INPUT") {
nextInput.value = "";
nextInput.focus();
}
this.updateCode();
}
}
handlePaste(event) {
const input = event.target;
if (!input || input.tagName !== "INPUT") return;
event.preventDefault();
const pasteData = (event.clipboardData || window.clipboardData).getData("text");
if (!/^\d+$/.test(pasteData)) return;
const inputs = this.inputs;
let pasteIndex = inputs.indexOf(input);
for (let i = 0; i < pasteData.length && pasteIndex + i < inputs.length; i++) {
inputs[pasteIndex + i].value = pasteData[i];
this.values[pasteIndex + i] = pasteData[i];
}
this.updateCode();
const nextInput = inputs[pasteIndex + pasteData.length - 1]?.nextElementSibling;
if (nextInput && nextInput.tagName === "INPUT") nextInput.focus();
}
}
if (!customElements.get("otp-input")) {
customElements.define("otp-input", OtpInput);
}
<h3>Web Component (Minimal)</h3>
<otp-input length="6"></otp-input>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment