A flexible and customizable One-Time Password (OTP) input component for Svelte 5+. Perfect for authentication flows, verification codes, 2FA (two-factor authentication), MFA (multi-factor authentication), PIN codes, and secure login experiences in your Svelte applications.
Looking for a Svelte 5 OTP component, verification code input, or 2FA input field? This package provides everything you need for modern authentication flows:
Use Cases: One-time passwords (OTP), email/SMS verification codes, two-factor authentication (2FA), multi-factor authentication (MFA), PIN code entry, secure access codes, payment verification, and more.
npm install svelte-otp-input
pnpm add svelte-otp-input
yarn add svelte-otp-input
Get up and running in seconds:
<script>
import OtpInput from 'svelte-otp-input';
let code = $state("");
</script>
<OtpInput
bind:value={code}
numInputs={6}
onComplete={(otp) => {
console.log('OTP entered:', otp);
// Verify OTP with your backend
}}
/>
That's it! See the full documentation for advanced features and customization options.
| Prop | Type | Description |
|---|---|---|
numInputs |
number |
Number of OTP input boxes (must be a positive integer) |
value |
string |
The OTP value (bindable with bind:value) |
| Prop | Type | Default | Description |
|---|---|---|---|
inputType |
string |
"number" |
Input type: "number", "text", "password", "upper-alnum", "uppercase", "lower-alnum", "lowercase", "alnum", or custom regex |
separator |
string | string[] | snippet |
"-" |
Character(s) or snippet between inputs |
groupSeparator |
string | snippet |
" " |
Separator between groups (when using group prop) |
group |
number[] |
null |
Array defining input grouping (e.g., [3, 3] for 6 inputs split into two groups) |
shouldAutoFocus |
boolean |
true |
Auto-focus first input on component mount |
placeholder |
string |
"" |
Placeholder text for empty inputs |
isError |
boolean |
false |
Show error state styling |
isDisabled |
boolean |
false |
Disable all inputs |
restrictPaste |
boolean |
false |
Prevent paste functionality for enhanced security |
| Prop | Type | Default | Description |
|---|---|---|---|
containerStyle |
string | object |
"" |
Styles for the container wrapper |
inputStyles |
string | object | Array<string | object> |
"" |
Styles for input boxes (supports Tailwind classes, CSS objects, or global styles) |
inputFocusStyle |
string | object |
"" |
Styles applied when input is focused |
inputErrorStyle |
string | object | Array<string | object> |
"" |
Styles applied when isError is true |
inputDisabledStyle |
string | object | Array<string | object> |
"" |
Styles applied when inputs are disabled |
placeholderStyle |
string | object |
"" |
Styles for placeholder text |
stylePriority |
object |
See below | Control which styles take precedence |
Default Style Priority:
{
inputDisabledStyle: 'p0', // Highest priority
inputErrorStyle: 'p1',
inputFocusStyle: 'p2' // Lowest priority
}
| Prop | Type | Description |
|---|---|---|
onComplete |
(value: string) => void |
Called when all inputs are filled |
onEnter |
(value: string) => void |
Called when Enter key is pressed |
onInput |
(event, index) => void |
Called on input change for each character |
onFocus |
(event, index) => void |
Called when an input receives focus |
onBlur |
(event, index) => void |
Called when an input loses focus |
onPaste |
(event, index) => void |
Called on paste event |
keyDown |
(event, index) => void |
Called on key down event |
| Prop | Type | Description |
|---|---|---|
inputRef |
Array |
Pass your own $state(Array(numInputs).fill(null)) to access input element references |
Perfect for simple verification codes:
<script>
import OtpInput from 'svelte-otp-input';
let otp = $state("");
</script>
<OtpInput
bind:value={otp}
numInputs={4}
placeholder="0"
/>
Commonly used for SMS verification and 2FA:
<script>
let otp = $state("");
</script>
<OtpInput
bind:value={otp}
numInputs={6}
group={[3, 3]}
groupSeparator="—"
/>
<p>Entered OTP: {otp}</p>
Create a beautiful, modern OTP input:
<OtpInput
bind:value={otp}
numInputs={4}
containerStyle="flex gap-4 justify-center py-8"
inputStyles="w-14 h-14 text-2xl font-bold text-center border-2 border-gray-300 rounded-lg"
inputFocusStyle="border-blue-500 ring-2 ring-blue-200 outline-none"
inputErrorStyle="border-red-500 bg-red-50"
/>
For precise control over appearance:
<OtpInput
bind:value={otp}
numInputs={4}
containerStyle={{
gap: '16px',
padding: '20px',
justifyContent: 'center'
}}
inputStyles={{
width: '60px',
height: '60px',
fontSize: '24px',
fontWeight: 'bold',
textAlign: 'center',
borderRadius: '8px',
border: '2px solid #e0e0e0',
backgroundColor: '#ffffff'
}}
inputFocusStyle={{
border: '2px solid #4CAF50',
boxShadow: '0 0 8px rgba(76, 175, 80, 0.3)',
outline: 'none'
}}
/>
Validate OTP and show errors:
<script>
import OtpInput from 'svelte-otp-input';
let otp = $state("");
let hasError = $state(false);
let isVerifying = $state(false);
async function handleComplete(value) {
isVerifying = true;
// Simulate API call
const isValid = await verifyOTP(value);
if (!isValid) {
hasError = true;
// Reset after showing error
setTimeout(() => {
otp = "";
hasError = false;
}, 2000);
} else {
hasError = false;
console.log("OTP verified successfully!");
}
isVerifying = false;
}
async function verifyOTP(code) {
// Your verification logic
return code === "123456";
}
</script>
<OtpInput
bind:value={otp}
numInputs={6}
isError={hasError}
isDisabled={isVerifying}
onComplete={handleComplete}
inputErrorStyle="border-red-500 bg-red-50 text-red-900"
/>
{#if hasError}
<p class="text-red-500 mt-2">Invalid OTP. Please try again.</p>
{/if}
{#if isVerifying}
<p class="text-gray-500 mt-2">Verifying...</p>
{/if}
For secure PIN entry:
<OtpInput
bind:value={otp}
numInputs={6}
inputType="password"
placeholder="•"
/>
Different separators between inputs:
<OtpInput
bind:value={otp}
numInputs={5}
separator={['-', '-', ' ', '-']}
/>
<!-- Renders as: X-X-X X-X -->
For alphabetic verification codes:
<OtpInput
bind:value={otp}
numInputs={4}
inputType="uppercase"
placeholder="A"
/>
For mixed character codes:
<OtpInput
bind:value={otp}
numInputs={8}
inputType="alnum"
group={[4, 4]}
groupSeparator=" - "
/>
Programmatically control inputs:
<script>
import OtpInput from 'svelte-otp-input';
let otp = $state("");
let inputRefs = $state(Array(6).fill(null));
function focusThirdInput() {
inputRefs[2]?.focus();
}
function clearAndFocusFirst() {
otp = "";
inputRefs[0]?.focus();
}
</script>
<OtpInput
bind:value={otp}
numInputs={6}
inputRef={inputRefs}
/>
<div class="mt-4 space-x-2">
<button onclick={focusThirdInput}>
Focus 3rd Input
</button>
<button onclick={clearAndFocusFirst}>
Clear & Reset
</button>
</div>
Submit on Enter key press:
<script>
let otp = $state("");
function handleEnter(value) {
if (value.length === 6) {
console.log("Submitting OTP:", value);
// Submit to your backend
}
}
</script>
<OtpInput
bind:value={otp}
numInputs={6}
onEnter={handleEnter}
/>
<p class="text-sm text-gray-500 mt-2">
Press Enter to submit
</p>
Prevent users from pasting codes:
<OtpInput
bind:value={otp}
numInputs={6}
restrictPaste={true}
/>
<p class="text-sm text-gray-500 mt-2">
Please enter each digit manually
</p>
The component supports various input types for different use cases:
| Type | Description | Allowed Characters | Example |
|---|---|---|---|
"number" |
Numeric only | 0-9 | 1234 |
"text" |
Any text | All characters | abcd |
"password" |
Masked input | All characters | •••• |
"upper-alnum" |
Uppercase alphanumeric | A-Z, 0-9 | A1B2 |
"uppercase" |
Uppercase letters only | A-Z | ABCD |
"lower-alnum" |
Lowercase alphanumeric | a-z, 0-9 | a1b2 |
"lowercase" |
Lowercase letters only | a-z | abcd |
"alnum" |
Alphanumeric (any case) | a-z, A-Z, 0-9 | Ab12 |
| Custom Regex | Custom pattern | Defined by your regex | Variable |
let regex = ^[A-F0-9]$
<OtpInput
bind:value={otp}
numInputs={4}
inputType={regex}
/>
<!-- Only accepts hexadecimal characters -->
Full keyboard support for accessibility:
onEnter callbackrestrictPaste is enabled)<OtpInput
bind:value={otp}
numInputs={6}
containerStyle="flex gap-3 p-6 bg-gray-50 rounded-xl"
inputStyles="w-12 h-12 text-xl font-semibold text-center border-2 border-gray-300 rounded-lg transition-all duration-200"
inputFocusStyle="border-blue-500 ring-4 ring-blue-100 scale-105"
inputErrorStyle="border-red-500 bg-red-50"
/>
<OtpInput
bind:value={otp}
numInputs={4}
inputStyles={{
width: '3rem',
height: '3rem',
fontSize: '1.5rem',
textAlign: 'center',
border: '2px solid #e5e7eb',
borderRadius: '0.5rem',
transition: 'all 0.2s'
}}
inputFocusStyle={{
borderColor: '#3b82f6',
boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.1)'
}}
/>
<OtpInput
bind:value={otp}
numInputs={6}
inputStyles="otp-input"
/>
<style>
:global(.otp-input) {
width: 3rem;
height: 3rem;
font-size: 1.5rem;
text-align: center;
border: 2px solid #e5e7eb;
border-radius: 0.5rem;
transition: all 0.2s ease;
}
:global(.otp-input:focus) {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
outline: none;
}
</style>
Yes! This component is built specifically for Svelte 5+ using modern runes ($state, $derived, etc.). It will not work with Svelte 4 or earlier versions.
Absolutely! The component supports mobile SMS autocomplete. On supported mobile browsers, users can tap to auto-fill the OTP from incoming SMS messages.
Yes! The component includes:
Completely! You can use:
:global() modifierUse the onComplete callback to validate when all digits are entered:
<OtpInput
bind:value={otp}
numInputs={6}
onComplete={async (code) => {
const isValid = await verifyWithBackend(code);
if (!isValid) {
// Handle invalid OTP
}
}}
/>
Yes! Simply bind the value and reset it:
<script>
import OtpInput, { setValue } from 'svelte-otp-input'
let otp = $state("");
function clearOTP() {
setValue("")
}
</script>
<OtpInput bind:value={otp} numInputs={6} />
<button onclick={clearOTP}>Clear</button>
Yes! The component is fully mobile-optimized with:
By default, pasting is enabled and will auto-fill all inputs. To disable:
<OtpInput
bind:value={otp}
numInputs={6}
restrictPaste={true}
/>
You can also use the onPaste callback to customize paste behavior.
Yes! You can use:
separator="-"separator={['-', '-', ' ', '-']}separator={customSnippet}<OtpInput
bind:value={pin}
numInputs={4}
inputType="password"
placeholder="•"
/>
Requirements: Svelte 5+
Contributions are welcome! Here's how you can help:
# Clone the repository
git clone https://github.com/yourusername/svelte-otp-input.git
# Install dependencies
npm install
# Run development server
npm run dev
# Run tests
npm test
# Build for production
npm run build
MIT © [Your Name]
See LICENSE for details.
Built with ❤️ for the Svelte community.
Keywords: svelte otp, svelte 5 otp input, svelte verification code, svelte 2fa input, svelte mfa, svelte pin code, svelte authentication component, one-time password svelte, verification input svelte, svelte security input, svelte otp component, two-factor authentication svelte, multi-factor authentication svelte, svelte sms verification, svelte code input
Made for Svelte 5+ | Zero Dependencies | Production Ready