Pin Input

OTP / 验证码输入 — N 个单字符框自动推进 / 退格回退 / 方向键导航 / 粘贴填充联动。首框预设 autocomplete="one-time-code"(iOS/Android 可显示短信码候选)。numeric(默认)/ 字母数字,length / size / masked / autofocus 等 prop。组合值通过隐藏 input 随表单提交。

最简 — 6 位 OTP(default)

Enter the 6-digit code we sent you

<x-pin-input name="otp" label="Verification code" hint="Enter the 6-digit code we sent you" />
<div class="w-full" x-data="{ digits: Array.from({ length: 6 }, (_, i) =&gt; ([])[i] ?? ''), get combined() { return this.digits.join(''); }, normalize(c) { if (!c) return ''; const ch = c.slice(-1); return /[0-9]/.test(ch) ? ch : ''; }, onInput(idx, e) { const ch = this.normalize(e.target.value); this.digits[idx] = ch; if (ch &amp;&amp; idx &lt; this.digits.length - 1) { this.$nextTick(() =&gt; this.$refs['pin' + (idx + 1)]?.focus()); } }, onKeydown(idx, e) { if (e.key === 'Backspace' &amp;&amp; !this.digits[idx] &amp;&amp; idx &gt; 0) { this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowLeft' &amp;&amp; idx &gt; 0) { e.preventDefault(); this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowRight' &amp;&amp; idx &lt; this.digits.length - 1) { e.preventDefault(); this.$refs['pin' + (idx + 1)]?.focus(); } }, onPaste(e) { e.preventDefault(); const txt = (e.clipboardData?.getData('text') || '').trim(); const chars = Array.from(txt).map(c =&gt; this.normalize(c)).filter(c =&gt; c); for (let i = 0; i &lt; this.digits.length; i++) { this.digits[i] = chars[i] ?? ''; } let lastFilled = -1; for (let i = 0; i &lt; this.digits.length; i++) { if (this.digits[i]) lastFilled = i; } const target = lastFilled === this.digits.length - 1 ? lastFilled : Math.min(lastFilled + 1, this.digits.length - 1); if (target &gt;= 0) this.$nextTick(() =&gt; this.$refs['pin' + target]?.focus()); }, }">
<label class="block mb-1.5 text-[length:var(--text-field-sm)] font-medium text-base-content">Verification code</label>
<div class="flex items-center gap-2">
<input type="text" x-model="digits[0]" x-ref="pin0" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="one-time-code" x-on:input="onInput(0, $event)" x-on:keydown="onKeydown(0, $event)" x-on:paste="onPaste($event)" aria-label="Digit 1" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[1]" x-ref="pin1" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(1, $event)" x-on:keydown="onKeydown(1, $event)" aria-label="Digit 2" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[2]" x-ref="pin2" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(2, $event)" x-on:keydown="onKeydown(2, $event)" aria-label="Digit 3" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[3]" x-ref="pin3" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(3, $event)" x-on:keydown="onKeydown(3, $event)" aria-label="Digit 4" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[4]" x-ref="pin4" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(4, $event)" x-on:keydown="onKeydown(4, $event)" aria-label="Digit 5" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[5]" x-ref="pin5" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(5, $event)" x-on:keydown="onKeydown(5, $event)" aria-label="Digit 6" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
</div><input type="hidden" name="otp" x-bind:value="combined">
<p class="mt-1.5 text-[length:var(--text-field-sm)] text-base-content/50">
        Enter the 6-digit code we sent you
    </p>
</div>

以 masked 方式显示 4 位 PIN

Digits are masked as you type

<x-pin-input name="pin" label="PIN" :length="4" masked hint="Digits are masked as you type" />
<div class="w-full" x-data="{ digits: Array.from({ length: 4 }, (_, i) =&gt; ([])[i] ?? ''), get combined() { return this.digits.join(''); }, normalize(c) { if (!c) return ''; const ch = c.slice(-1); return /[0-9]/.test(ch) ? ch : ''; }, onInput(idx, e) { const ch = this.normalize(e.target.value); this.digits[idx] = ch; if (ch &amp;&amp; idx &lt; this.digits.length - 1) { this.$nextTick(() =&gt; this.$refs['pin' + (idx + 1)]?.focus()); } }, onKeydown(idx, e) { if (e.key === 'Backspace' &amp;&amp; !this.digits[idx] &amp;&amp; idx &gt; 0) { this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowLeft' &amp;&amp; idx &gt; 0) { e.preventDefault(); this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowRight' &amp;&amp; idx &lt; this.digits.length - 1) { e.preventDefault(); this.$refs['pin' + (idx + 1)]?.focus(); } }, onPaste(e) { e.preventDefault(); const txt = (e.clipboardData?.getData('text') || '').trim(); const chars = Array.from(txt).map(c =&gt; this.normalize(c)).filter(c =&gt; c); for (let i = 0; i &lt; this.digits.length; i++) { this.digits[i] = chars[i] ?? ''; } let lastFilled = -1; for (let i = 0; i &lt; this.digits.length; i++) { if (this.digits[i]) lastFilled = i; } const target = lastFilled === this.digits.length - 1 ? lastFilled : Math.min(lastFilled + 1, this.digits.length - 1); if (target &gt;= 0) this.$nextTick(() =&gt; this.$refs['pin' + target]?.focus()); }, }">
<label class="block mb-1.5 text-[length:var(--text-field-sm)] font-medium text-base-content">PIN</label>
<div class="flex items-center gap-2">
<input type="password" x-model="digits[0]" x-ref="pin0" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="one-time-code" x-on:input="onInput(0, $event)" x-on:keydown="onKeydown(0, $event)" x-on:paste="onPaste($event)" aria-label="Digit 1" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="password" x-model="digits[1]" x-ref="pin1" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(1, $event)" x-on:keydown="onKeydown(1, $event)" aria-label="Digit 2" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="password" x-model="digits[2]" x-ref="pin2" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(2, $event)" x-on:keydown="onKeydown(2, $event)" aria-label="Digit 3" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="password" x-model="digits[3]" x-ref="pin3" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(3, $event)" x-on:keydown="onKeydown(3, $event)" aria-label="Digit 4" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
</div><input type="hidden" name="pin" x-bind:value="combined">
<p class="mt-1.5 text-[length:var(--text-field-sm)] text-base-content/50">
        Digits are masked as you type
    </p>
</div>

8 位英数字邀请码

Letters and digits, case-insensitive

<x-pin-input name="invite" label="Invite code" :length="8" type="alphanumeric" size="sm" hint="Letters and digits, case-insensitive" />
<div class="w-full" x-data="{ digits: Array.from({ length: 8 }, (_, i) =&gt; ([])[i] ?? ''), get combined() { return this.digits.join(''); }, normalize(c) { if (!c) return ''; const ch = c.slice(-1); return /[0-9a-zA-Z]/.test(ch) ? ch : ''; }, onInput(idx, e) { const ch = this.normalize(e.target.value); this.digits[idx] = ch; if (ch &amp;&amp; idx &lt; this.digits.length - 1) { this.$nextTick(() =&gt; this.$refs['pin' + (idx + 1)]?.focus()); } }, onKeydown(idx, e) { if (e.key === 'Backspace' &amp;&amp; !this.digits[idx] &amp;&amp; idx &gt; 0) { this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowLeft' &amp;&amp; idx &gt; 0) { e.preventDefault(); this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowRight' &amp;&amp; idx &lt; this.digits.length - 1) { e.preventDefault(); this.$refs['pin' + (idx + 1)]?.focus(); } }, onPaste(e) { e.preventDefault(); const txt = (e.clipboardData?.getData('text') || '').trim(); const chars = Array.from(txt).map(c =&gt; this.normalize(c)).filter(c =&gt; c); for (let i = 0; i &lt; this.digits.length; i++) { this.digits[i] = chars[i] ?? ''; } let lastFilled = -1; for (let i = 0; i &lt; this.digits.length; i++) { if (this.digits[i]) lastFilled = i; } const target = lastFilled === this.digits.length - 1 ? lastFilled : Math.min(lastFilled + 1, this.digits.length - 1); if (target &gt;= 0) this.$nextTick(() =&gt; this.$refs['pin' + target]?.focus()); }, }">
<label class="block mb-1.5 text-[length:var(--text-field-sm)] font-medium text-base-content">Invite code</label>
<div class="flex items-center gap-2">
<input type="text" x-model="digits[0]" x-ref="pin0" pattern="[0-9a-zA-Z]" inputmode="text" maxlength="1" autocomplete="one-time-code" x-on:input="onInput(0, $event)" x-on:keydown="onKeydown(0, $event)" x-on:paste="onPaste($event)" aria-label="Digit 1" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-10 h-[var(--h-field-sm)] text-lg tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[1]" x-ref="pin1" pattern="[0-9a-zA-Z]" inputmode="text" maxlength="1" autocomplete="off" x-on:input="onInput(1, $event)" x-on:keydown="onKeydown(1, $event)" aria-label="Digit 2" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-10 h-[var(--h-field-sm)] text-lg tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[2]" x-ref="pin2" pattern="[0-9a-zA-Z]" inputmode="text" maxlength="1" autocomplete="off" x-on:input="onInput(2, $event)" x-on:keydown="onKeydown(2, $event)" aria-label="Digit 3" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-10 h-[var(--h-field-sm)] text-lg tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[3]" x-ref="pin3" pattern="[0-9a-zA-Z]" inputmode="text" maxlength="1" autocomplete="off" x-on:input="onInput(3, $event)" x-on:keydown="onKeydown(3, $event)" aria-label="Digit 4" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-10 h-[var(--h-field-sm)] text-lg tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[4]" x-ref="pin4" pattern="[0-9a-zA-Z]" inputmode="text" maxlength="1" autocomplete="off" x-on:input="onInput(4, $event)" x-on:keydown="onKeydown(4, $event)" aria-label="Digit 5" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-10 h-[var(--h-field-sm)] text-lg tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[5]" x-ref="pin5" pattern="[0-9a-zA-Z]" inputmode="text" maxlength="1" autocomplete="off" x-on:input="onInput(5, $event)" x-on:keydown="onKeydown(5, $event)" aria-label="Digit 6" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-10 h-[var(--h-field-sm)] text-lg tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[6]" x-ref="pin6" pattern="[0-9a-zA-Z]" inputmode="text" maxlength="1" autocomplete="off" x-on:input="onInput(6, $event)" x-on:keydown="onKeydown(6, $event)" aria-label="Digit 7" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-10 h-[var(--h-field-sm)] text-lg tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[7]" x-ref="pin7" pattern="[0-9a-zA-Z]" inputmode="text" maxlength="1" autocomplete="off" x-on:input="onInput(7, $event)" x-on:keydown="onKeydown(7, $event)" aria-label="Digit 8" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-10 h-[var(--h-field-sm)] text-lg tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
</div><input type="hidden" name="invite" x-bind:value="combined">
<p class="mt-1.5 text-[length:var(--text-field-sm)] text-base-content/50">
        Letters and digits, case-insensitive
    </p>
</div>

设置初始值

The remaining cells stay empty and focusable

<x-pin-input name="otp_prefilled" label="Code" :length="6" value="123" hint="The remaining cells stay empty and focusable" />
<div class="w-full" x-data="{ digits: Array.from({ length: 6 }, (_, i) =&gt; ([&quot;1&quot;,&quot;2&quot;,&quot;3&quot;])[i] ?? ''), get combined() { return this.digits.join(''); }, normalize(c) { if (!c) return ''; const ch = c.slice(-1); return /[0-9]/.test(ch) ? ch : ''; }, onInput(idx, e) { const ch = this.normalize(e.target.value); this.digits[idx] = ch; if (ch &amp;&amp; idx &lt; this.digits.length - 1) { this.$nextTick(() =&gt; this.$refs['pin' + (idx + 1)]?.focus()); } }, onKeydown(idx, e) { if (e.key === 'Backspace' &amp;&amp; !this.digits[idx] &amp;&amp; idx &gt; 0) { this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowLeft' &amp;&amp; idx &gt; 0) { e.preventDefault(); this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowRight' &amp;&amp; idx &lt; this.digits.length - 1) { e.preventDefault(); this.$refs['pin' + (idx + 1)]?.focus(); } }, onPaste(e) { e.preventDefault(); const txt = (e.clipboardData?.getData('text') || '').trim(); const chars = Array.from(txt).map(c =&gt; this.normalize(c)).filter(c =&gt; c); for (let i = 0; i &lt; this.digits.length; i++) { this.digits[i] = chars[i] ?? ''; } let lastFilled = -1; for (let i = 0; i &lt; this.digits.length; i++) { if (this.digits[i]) lastFilled = i; } const target = lastFilled === this.digits.length - 1 ? lastFilled : Math.min(lastFilled + 1, this.digits.length - 1); if (target &gt;= 0) this.$nextTick(() =&gt; this.$refs['pin' + target]?.focus()); }, }">
<label class="block mb-1.5 text-[length:var(--text-field-sm)] font-medium text-base-content">Code</label>
<div class="flex items-center gap-2">
<input type="text" x-model="digits[0]" x-ref="pin0" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="one-time-code" x-on:input="onInput(0, $event)" x-on:keydown="onKeydown(0, $event)" x-on:paste="onPaste($event)" aria-label="Digit 1" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[1]" x-ref="pin1" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(1, $event)" x-on:keydown="onKeydown(1, $event)" aria-label="Digit 2" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[2]" x-ref="pin2" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(2, $event)" x-on:keydown="onKeydown(2, $event)" aria-label="Digit 3" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[3]" x-ref="pin3" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(3, $event)" x-on:keydown="onKeydown(3, $event)" aria-label="Digit 4" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[4]" x-ref="pin4" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(4, $event)" x-on:keydown="onKeydown(4, $event)" aria-label="Digit 5" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[5]" x-ref="pin5" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(5, $event)" x-on:keydown="onKeydown(5, $event)" aria-label="Digit 6" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
</div><input type="hidden" name="otp_prefilled" x-bind:value="combined">
<p class="mt-1.5 text-[length:var(--text-field-sm)] text-base-content/50">
        The remaining cells stay empty and focusable
    </p>
</div>

size — xs / sm / md (default) / lg

<x-pin-input size="xs" :length="4" />
<x-pin-input size="sm" :length="4" />
<x-pin-input size="md" :length="4" />
<x-pin-input size="lg" :length="4" />
<div class="w-full" x-data="{ digits: Array.from({ length: 4 }, (_, i) =&gt; ([])[i] ?? ''), get combined() { return this.digits.join(''); }, normalize(c) { if (!c) return ''; const ch = c.slice(-1); return /[0-9]/.test(ch) ? ch : ''; }, onInput(idx, e) { const ch = this.normalize(e.target.value); this.digits[idx] = ch; if (ch &amp;&amp; idx &lt; this.digits.length - 1) { this.$nextTick(() =&gt; this.$refs['pin' + (idx + 1)]?.focus()); } }, onKeydown(idx, e) { if (e.key === 'Backspace' &amp;&amp; !this.digits[idx] &amp;&amp; idx &gt; 0) { this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowLeft' &amp;&amp; idx &gt; 0) { e.preventDefault(); this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowRight' &amp;&amp; idx &lt; this.digits.length - 1) { e.preventDefault(); this.$refs['pin' + (idx + 1)]?.focus(); } }, onPaste(e) { e.preventDefault(); const txt = (e.clipboardData?.getData('text') || '').trim(); const chars = Array.from(txt).map(c =&gt; this.normalize(c)).filter(c =&gt; c); for (let i = 0; i &lt; this.digits.length; i++) { this.digits[i] = chars[i] ?? ''; } let lastFilled = -1; for (let i = 0; i &lt; this.digits.length; i++) { if (this.digits[i]) lastFilled = i; } const target = lastFilled === this.digits.length - 1 ? lastFilled : Math.min(lastFilled + 1, this.digits.length - 1); if (target &gt;= 0) this.$nextTick(() =&gt; this.$refs['pin' + target]?.focus()); }, }">
<div class="flex items-center gap-2">
<input type="text" x-model="digits[0]" x-ref="pin0" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="one-time-code" x-on:input="onInput(0, $event)" x-on:keydown="onKeydown(0, $event)" x-on:paste="onPaste($event)" aria-label="Digit 1" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-8 h-[var(--h-field-xs)] text-base tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[1]" x-ref="pin1" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(1, $event)" x-on:keydown="onKeydown(1, $event)" aria-label="Digit 2" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-8 h-[var(--h-field-xs)] text-base tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[2]" x-ref="pin2" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(2, $event)" x-on:keydown="onKeydown(2, $event)" aria-label="Digit 3" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-8 h-[var(--h-field-xs)] text-base tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[3]" x-ref="pin3" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(3, $event)" x-on:keydown="onKeydown(3, $event)" aria-label="Digit 4" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-8 h-[var(--h-field-xs)] text-base tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
</div>
</div>
<div class="w-full" x-data="{ digits: Array.from({ length: 4 }, (_, i) =&gt; ([])[i] ?? ''), get combined() { return this.digits.join(''); }, normalize(c) { if (!c) return ''; const ch = c.slice(-1); return /[0-9]/.test(ch) ? ch : ''; }, onInput(idx, e) { const ch = this.normalize(e.target.value); this.digits[idx] = ch; if (ch &amp;&amp; idx &lt; this.digits.length - 1) { this.$nextTick(() =&gt; this.$refs['pin' + (idx + 1)]?.focus()); } }, onKeydown(idx, e) { if (e.key === 'Backspace' &amp;&amp; !this.digits[idx] &amp;&amp; idx &gt; 0) { this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowLeft' &amp;&amp; idx &gt; 0) { e.preventDefault(); this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowRight' &amp;&amp; idx &lt; this.digits.length - 1) { e.preventDefault(); this.$refs['pin' + (idx + 1)]?.focus(); } }, onPaste(e) { e.preventDefault(); const txt = (e.clipboardData?.getData('text') || '').trim(); const chars = Array.from(txt).map(c =&gt; this.normalize(c)).filter(c =&gt; c); for (let i = 0; i &lt; this.digits.length; i++) { this.digits[i] = chars[i] ?? ''; } let lastFilled = -1; for (let i = 0; i &lt; this.digits.length; i++) { if (this.digits[i]) lastFilled = i; } const target = lastFilled === this.digits.length - 1 ? lastFilled : Math.min(lastFilled + 1, this.digits.length - 1); if (target &gt;= 0) this.$nextTick(() =&gt; this.$refs['pin' + target]?.focus()); }, }">
<div class="flex items-center gap-2">
<input type="text" x-model="digits[0]" x-ref="pin0" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="one-time-code" x-on:input="onInput(0, $event)" x-on:keydown="onKeydown(0, $event)" x-on:paste="onPaste($event)" aria-label="Digit 1" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-10 h-[var(--h-field-sm)] text-lg tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[1]" x-ref="pin1" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(1, $event)" x-on:keydown="onKeydown(1, $event)" aria-label="Digit 2" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-10 h-[var(--h-field-sm)] text-lg tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[2]" x-ref="pin2" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(2, $event)" x-on:keydown="onKeydown(2, $event)" aria-label="Digit 3" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-10 h-[var(--h-field-sm)] text-lg tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[3]" x-ref="pin3" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(3, $event)" x-on:keydown="onKeydown(3, $event)" aria-label="Digit 4" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-10 h-[var(--h-field-sm)] text-lg tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
</div>
</div>
<div class="w-full" x-data="{ digits: Array.from({ length: 4 }, (_, i) =&gt; ([])[i] ?? ''), get combined() { return this.digits.join(''); }, normalize(c) { if (!c) return ''; const ch = c.slice(-1); return /[0-9]/.test(ch) ? ch : ''; }, onInput(idx, e) { const ch = this.normalize(e.target.value); this.digits[idx] = ch; if (ch &amp;&amp; idx &lt; this.digits.length - 1) { this.$nextTick(() =&gt; this.$refs['pin' + (idx + 1)]?.focus()); } }, onKeydown(idx, e) { if (e.key === 'Backspace' &amp;&amp; !this.digits[idx] &amp;&amp; idx &gt; 0) { this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowLeft' &amp;&amp; idx &gt; 0) { e.preventDefault(); this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowRight' &amp;&amp; idx &lt; this.digits.length - 1) { e.preventDefault(); this.$refs['pin' + (idx + 1)]?.focus(); } }, onPaste(e) { e.preventDefault(); const txt = (e.clipboardData?.getData('text') || '').trim(); const chars = Array.from(txt).map(c =&gt; this.normalize(c)).filter(c =&gt; c); for (let i = 0; i &lt; this.digits.length; i++) { this.digits[i] = chars[i] ?? ''; } let lastFilled = -1; for (let i = 0; i &lt; this.digits.length; i++) { if (this.digits[i]) lastFilled = i; } const target = lastFilled === this.digits.length - 1 ? lastFilled : Math.min(lastFilled + 1, this.digits.length - 1); if (target &gt;= 0) this.$nextTick(() =&gt; this.$refs['pin' + target]?.focus()); }, }">
<div class="flex items-center gap-2">
<input type="text" x-model="digits[0]" x-ref="pin0" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="one-time-code" x-on:input="onInput(0, $event)" x-on:keydown="onKeydown(0, $event)" x-on:paste="onPaste($event)" aria-label="Digit 1" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[1]" x-ref="pin1" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(1, $event)" x-on:keydown="onKeydown(1, $event)" aria-label="Digit 2" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[2]" x-ref="pin2" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(2, $event)" x-on:keydown="onKeydown(2, $event)" aria-label="Digit 3" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[3]" x-ref="pin3" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(3, $event)" x-on:keydown="onKeydown(3, $event)" aria-label="Digit 4" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
</div>
</div>
<div class="w-full" x-data="{ digits: Array.from({ length: 4 }, (_, i) =&gt; ([])[i] ?? ''), get combined() { return this.digits.join(''); }, normalize(c) { if (!c) return ''; const ch = c.slice(-1); return /[0-9]/.test(ch) ? ch : ''; }, onInput(idx, e) { const ch = this.normalize(e.target.value); this.digits[idx] = ch; if (ch &amp;&amp; idx &lt; this.digits.length - 1) { this.$nextTick(() =&gt; this.$refs['pin' + (idx + 1)]?.focus()); } }, onKeydown(idx, e) { if (e.key === 'Backspace' &amp;&amp; !this.digits[idx] &amp;&amp; idx &gt; 0) { this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowLeft' &amp;&amp; idx &gt; 0) { e.preventDefault(); this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowRight' &amp;&amp; idx &lt; this.digits.length - 1) { e.preventDefault(); this.$refs['pin' + (idx + 1)]?.focus(); } }, onPaste(e) { e.preventDefault(); const txt = (e.clipboardData?.getData('text') || '').trim(); const chars = Array.from(txt).map(c =&gt; this.normalize(c)).filter(c =&gt; c); for (let i = 0; i &lt; this.digits.length; i++) { this.digits[i] = chars[i] ?? ''; } let lastFilled = -1; for (let i = 0; i &lt; this.digits.length; i++) { if (this.digits[i]) lastFilled = i; } const target = lastFilled === this.digits.length - 1 ? lastFilled : Math.min(lastFilled + 1, this.digits.length - 1); if (target &gt;= 0) this.$nextTick(() =&gt; this.$refs['pin' + target]?.focus()); }, }">
<div class="flex items-center gap-2">
<input type="text" x-model="digits[0]" x-ref="pin0" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="one-time-code" x-on:input="onInput(0, $event)" x-on:keydown="onKeydown(0, $event)" x-on:paste="onPaste($event)" aria-label="Digit 1" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-14 h-[var(--h-field-lg)] text-2xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[1]" x-ref="pin1" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(1, $event)" x-on:keydown="onKeydown(1, $event)" aria-label="Digit 2" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-14 h-[var(--h-field-lg)] text-2xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[2]" x-ref="pin2" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(2, $event)" x-on:keydown="onKeydown(2, $event)" aria-label="Digit 3" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-14 h-[var(--h-field-lg)] text-2xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[3]" x-ref="pin3" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(3, $event)" x-on:keydown="onKeydown(3, $event)" aria-label="Digit 4" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-14 h-[var(--h-field-lg)] text-2xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
</div>
</div>

error state — box border 变为 error

Code expired — request a new one

<x-pin-input name="otp_error" label="Verification code" :length="6" error="Code expired — request a new one" />
<div class="w-full" x-data="{ digits: Array.from({ length: 6 }, (_, i) =&gt; ([])[i] ?? ''), get combined() { return this.digits.join(''); }, normalize(c) { if (!c) return ''; const ch = c.slice(-1); return /[0-9]/.test(ch) ? ch : ''; }, onInput(idx, e) { const ch = this.normalize(e.target.value); this.digits[idx] = ch; if (ch &amp;&amp; idx &lt; this.digits.length - 1) { this.$nextTick(() =&gt; this.$refs['pin' + (idx + 1)]?.focus()); } }, onKeydown(idx, e) { if (e.key === 'Backspace' &amp;&amp; !this.digits[idx] &amp;&amp; idx &gt; 0) { this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowLeft' &amp;&amp; idx &gt; 0) { e.preventDefault(); this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowRight' &amp;&amp; idx &lt; this.digits.length - 1) { e.preventDefault(); this.$refs['pin' + (idx + 1)]?.focus(); } }, onPaste(e) { e.preventDefault(); const txt = (e.clipboardData?.getData('text') || '').trim(); const chars = Array.from(txt).map(c =&gt; this.normalize(c)).filter(c =&gt; c); for (let i = 0; i &lt; this.digits.length; i++) { this.digits[i] = chars[i] ?? ''; } let lastFilled = -1; for (let i = 0; i &lt; this.digits.length; i++) { if (this.digits[i]) lastFilled = i; } const target = lastFilled === this.digits.length - 1 ? lastFilled : Math.min(lastFilled + 1, this.digits.length - 1); if (target &gt;= 0) this.$nextTick(() =&gt; this.$refs['pin' + target]?.focus()); }, }">
<label class="block mb-1.5 text-[length:var(--text-field-sm)] font-medium text-error">Verification code</label>
<div class="flex items-center gap-2">
<input type="text" x-model="digits[0]" x-ref="pin0" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="one-time-code" x-on:input="onInput(0, $event)" x-on:keydown="onKeydown(0, $event)" x-on:paste="onPaste($event)" aria-label="Digit 1" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-error focus:border-error focus:ring-2 focus:ring-error/30">
<input type="text" x-model="digits[1]" x-ref="pin1" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(1, $event)" x-on:keydown="onKeydown(1, $event)" aria-label="Digit 2" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-error focus:border-error focus:ring-2 focus:ring-error/30">
<input type="text" x-model="digits[2]" x-ref="pin2" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(2, $event)" x-on:keydown="onKeydown(2, $event)" aria-label="Digit 3" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-error focus:border-error focus:ring-2 focus:ring-error/30">
<input type="text" x-model="digits[3]" x-ref="pin3" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(3, $event)" x-on:keydown="onKeydown(3, $event)" aria-label="Digit 4" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-error focus:border-error focus:ring-2 focus:ring-error/30">
<input type="text" x-model="digits[4]" x-ref="pin4" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(4, $event)" x-on:keydown="onKeydown(4, $event)" aria-label="Digit 5" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-error focus:border-error focus:ring-2 focus:ring-error/30">
<input type="text" x-model="digits[5]" x-ref="pin5" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(5, $event)" x-on:keydown="onKeydown(5, $event)" aria-label="Digit 6" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-error focus:border-error focus:ring-2 focus:ring-error/30">
</div><input type="hidden" name="otp_error" x-bind:value="combined">
<p class="mt-1.5 text-[length:var(--text-field-sm)] text-error">
        Code expired — request a new one
    </p>
</div>

试试粘贴 — 复制任意 6 位数字并粘贴到第一个 box,所有 box 会一次性填满

Paste the whole code at once — it spreads across the cells

<x-pin-input :length="6" hint="Paste the whole code at once — it spreads across the cells" />
<div class="w-full" x-data="{ digits: Array.from({ length: 6 }, (_, i) =&gt; ([])[i] ?? ''), get combined() { return this.digits.join(''); }, normalize(c) { if (!c) return ''; const ch = c.slice(-1); return /[0-9]/.test(ch) ? ch : ''; }, onInput(idx, e) { const ch = this.normalize(e.target.value); this.digits[idx] = ch; if (ch &amp;&amp; idx &lt; this.digits.length - 1) { this.$nextTick(() =&gt; this.$refs['pin' + (idx + 1)]?.focus()); } }, onKeydown(idx, e) { if (e.key === 'Backspace' &amp;&amp; !this.digits[idx] &amp;&amp; idx &gt; 0) { this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowLeft' &amp;&amp; idx &gt; 0) { e.preventDefault(); this.$refs['pin' + (idx - 1)]?.focus(); } else if (e.key === 'ArrowRight' &amp;&amp; idx &lt; this.digits.length - 1) { e.preventDefault(); this.$refs['pin' + (idx + 1)]?.focus(); } }, onPaste(e) { e.preventDefault(); const txt = (e.clipboardData?.getData('text') || '').trim(); const chars = Array.from(txt).map(c =&gt; this.normalize(c)).filter(c =&gt; c); for (let i = 0; i &lt; this.digits.length; i++) { this.digits[i] = chars[i] ?? ''; } let lastFilled = -1; for (let i = 0; i &lt; this.digits.length; i++) { if (this.digits[i]) lastFilled = i; } const target = lastFilled === this.digits.length - 1 ? lastFilled : Math.min(lastFilled + 1, this.digits.length - 1); if (target &gt;= 0) this.$nextTick(() =&gt; this.$refs['pin' + target]?.focus()); }, }">
<div class="flex items-center gap-2">
<input type="text" x-model="digits[0]" x-ref="pin0" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="one-time-code" x-on:input="onInput(0, $event)" x-on:keydown="onKeydown(0, $event)" x-on:paste="onPaste($event)" aria-label="Digit 1" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[1]" x-ref="pin1" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(1, $event)" x-on:keydown="onKeydown(1, $event)" aria-label="Digit 2" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[2]" x-ref="pin2" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(2, $event)" x-on:keydown="onKeydown(2, $event)" aria-label="Digit 3" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[3]" x-ref="pin3" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(3, $event)" x-on:keydown="onKeydown(3, $event)" aria-label="Digit 4" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[4]" x-ref="pin4" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(4, $event)" x-on:keydown="onKeydown(4, $event)" aria-label="Digit 5" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
<input type="text" x-model="digits[5]" x-ref="pin5" pattern="[0-9]" inputmode="numeric" maxlength="1" autocomplete="off" x-on:input="onInput(5, $event)" x-on:keydown="onKeydown(5, $event)" aria-label="Digit 6" class="rounded-[var(--radius-field)] bg-base-100 text-center font-semibold tabular-nums focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed w-12 h-[var(--h-field-md)] text-xl tune-border border-base-300 focus:border-primary focus:ring-2 focus:ring-primary/30">
</div>
<p class="mt-1.5 text-[length:var(--text-field-sm)] text-base-content/50">
        Paste the whole code at once — it spreads across the cells
    </p>
</div>