Skip to content

Instantly share code, notes, and snippets.

@andreasvirkus
Created September 1, 2025 19:24
Show Gist options
  • Save andreasvirkus/9f0ab367e688e5cc1058e97473584efb to your computer and use it in GitHub Desktop.
Save andreasvirkus/9f0ab367e688e5cc1058e97473584efb to your computer and use it in GitHub Desktop.
<template>
<div class="otp-input my-4 flex justify-center gap-2 text-black">
<input
v-for="i of 6"
:key="i"
v-model="otp[i - 1]"
type="tel"
maxlength="1"
class="otp-field text-heading"
required
@input="($event) => handleInput($event, i - 1)"
@paste="handlePaste"
@keydown.enter="handleSubmit"
@keydown.right="focusNext(i - 1)"
@keydown.left="focusPrev(i - 1)"
@keydown.delete="focusPrev(i - 1, true)"
/>
</div>
</template>
<script setup lang="ts">
const emit = defineEmits<{
(event: 'update', otp: string): void
}>()
const otp = ref(['', '', '', '', '', ''])
const otpToken = computed(() => otp.value.join(''))
watch(otpToken, (val) => {
if (val.length === 6) emit('update', val)
})
const handleInput = (event: Event, i: number) => {
const val = (event.target as HTMLInputElement).value
if (isNaN(Number(val))) {
otp.value[i] = ''
return
}
if ((event.target as HTMLInputElement).value) focusNext(i)
}
const focusNext = (index: number) => {
const nextInput = document.querySelectorAll('.otp-field')[index + 1]
;(nextInput as HTMLElement)?.focus()
}
const focusPrev = (index: number, clearField = false) => {
if (clearField && !!otp.value[index]) {
otp.value[index] = ''
return
}
const prevInput = document.querySelectorAll('.otp-field')[index - 1]
;(prevInput as HTMLElement)?.focus()
}
const handlePaste = (event: ClipboardEvent) => {
const pastedData = event.clipboardData?.getData('text')?.trim() ?? ''
if (pastedData.length === 6) {
otp.value = pastedData.split('')
// Focus the last input field after pasting
;(document.querySelector('.otp-field:last-child') as HTMLElement)?.focus()
}
}
const handleSubmit = () => {
if (otpToken.value.length < otp.value.length) return
emit('update', otpToken.value)
}
</script>
<style>
.otp-field {
width: 40px;
height: 70px;
border-radius: 8px;
border: 2px solid #ccc;
text-align: center;
font-size: 40px;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment