svelte-otp-input
v0.2.1
Published
One-time password input component for Svelte.
Downloads
30
Readme
OTP Input for Svelte
WIP. Use at your own risk.
One time passcode Input. Accessible & unstyled. Based on the React version by guilhermerodz.
Installation
npm install svelte-otp-input
Usage
<script>
import { OTPInput } from 'svelte-otp-input';
import Slot from '$lib/components/Slot.svelte';
let value = '123456';
</script>
<OTPInput {value} maxlength={6} let:slots>
<!-- slots -->
</OTPInput>
Default example
The example below uses tailwindcss
, shadcn-svelte
, tailwind-merge
and clsx
:
<script lang="ts">
import { OTPInput } from 'svelte-otp-input';
import Slot from '$lib/components/Slot.svelte';
</script>
<OTPInput
maxlength={6}
containerClass="group flex items-center has-[:disabled]:opacity-30"
let:slots
>
<div class="flex">
{#each slots.slice(0, 3) as slot, idx}
<Slot {...slot} />
{/each}
</div>
<!-- Fake Dash. Inspired by Stripe's MFA input. -->
<div class="flex w-10 items-center justify-center">
<div class="h-1 w-3 rounded-full bg-border" />
</div>
<div class="flex">
{#each slots.slice(3) as slot, idx}
<Slot {...slot} />
{/each}
</div>
</OTPInput>
<script lang="ts">
// Slot.svelte
export let char: string | null = null;
export let isActive: boolean;
</script>
<div
class={cn(
'relative h-14 w-10 text-[2rem]',
'flex items-center justify-center',
'transition-all duration-300',
'border-y border-r border-border first:rounded-l-md first:border-l last:rounded-r-md',
'group-focus-within:border-accent-foreground/20 group-hover:border-accent-foreground/20',
'outline outline-0 outline-accent-foreground/20',
{ 'outline-4 outline-accent-foreground': isActive }
)}
>
{#if char !== null}
<div>
{{ char }}
</div>
{/if}
<!-- Emulate a Fake Caret -->
{#if char === null && isActive}
<div
class="pointer-events-none absolute inset-0 flex animate-caret-blink items-center justify-center"
>
<div class="h-8 w-px bg-white" />
</div>
{/if}
</div>
// tailwind.config.ts for the blinking caret animation.
const config = {
theme: {
extend: {
keyframes: {
'caret-blink': {
'0%,70%,100%': { opacity: '1' },
'20%,50%': { opacity: '0' }
}
},
animation: {
'caret-blink': 'caret-blink 1.2s ease-out infinite'
}
}
}
};
// Small utility to merge class names.
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import type { ClassValue } from 'clsx';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
How it works
There's currently no native OTP/2FA/MFA input in HTML, which means people are either going with 1. a simple input design or 2. custom designs like this one. This library works by rendering an invisible input as a sibling of the slots, contained by a relative
ly positioned parent (the container root called OTPInput).
API Reference
OTPInput
The root container. Define settings for the input via props. Then, use the default slot to create the slots.
type OTPInputProps = {
// The number of slots
maxlength: number
// The class name for the root container
containerClass?: string
// Value state controlling the input
value?: string
// Setter for the controlled value (or callback for uncontrolled value)
oninput?: (newValue: string) => unknown
// Callback when the input is complete
oncomplete?: (...args: any[]) => unknown
// Where is the text located within the input
// Affects click-holding or long-press behavior
// Default: 'left'
textAlign?: 'left' | 'center' | 'right'
// Virtual keyboard appearance on mobile
// Default: 'numeric'
inputmode?: 'numeric' | 'text'
}
License
MIT