Gathering detailed insights and metrics for input-otp
Gathering detailed insights and metrics for input-otp
Gathering detailed insights and metrics for input-otp
Gathering detailed insights and metrics for input-otp
vue-input-otp
https://github.com/wobsoriano/vue-input-otp/assets/13049130/c5080f41-f411-4d38-aa57-d04d90c832c3
react18-input-otp
A fully customizable, one-time password (OTP) and phone number with separator input component for the web built with React.
react-native-verify-otp-inputs
react-native-verify-otp is a tiny Javascript library which provides an elegant UI for the end user to input OTP
antd-input-otp
An OTP Input Component based on Ant Design Component Library for React.
npm install input-otp
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
2,537 Stars
288 Commits
57 Forks
2 Watching
5 Branches
8 Contributors
Updated on 27 Nov 2024
TypeScript (85.19%)
CSS (5.69%)
MDX (4.81%)
JavaScript (3.99%)
HTML (0.32%)
Cumulative downloads
Total Downloads
Last day
26.7%
71,436
Compared to previous day
Last week
23.8%
372,606
Compared to previous week
Last month
35.6%
1,306,747
Compared to previous month
Last year
0%
5,695,924
Compared to previous year
5
https://github.com/guilhermerodz/input-otp/assets/10366880/753751f5-eda8-4145-a4b9-7ef51ca5e453
1npm install input-otp
Then import the component.
1+'use client' 2+import { OTPInput } from 'input-otp' 3 4function MyForm() { 5 return <form> 6+ <OTPInput maxLength={6} render={({slots}) => (...)} /> 7 </form> 8}
The example below uses tailwindcss
@shadcn/ui
tailwind-merge
clsx
:
1'use client' 2import { OTPInput, SlotProps } from 'input-otp' 3<OTPInput 4 maxLength={6} 5 containerClassName="group flex items-center has-[:disabled]:opacity-30" 6 render={({ slots }) => ( 7 <> 8 <div className="flex"> 9 {slots.slice(0, 3).map((slot, idx) => ( 10 <Slot key={idx} {...slot} /> 11 ))} 12 </div> 13 14 <FakeDash /> 15 16 <div className="flex"> 17 {slots.slice(3).map((slot, idx) => ( 18 <Slot key={idx} {...slot} /> 19 ))} 20 </div> 21 </> 22 )} 23/> 24 25// Feel free to copy. Uses @shadcn/ui tailwind colors. 26function Slot(props: SlotProps) { 27 return ( 28 <div 29 className={cn( 30 'relative w-10 h-14 text-[2rem]', 31 'flex items-center justify-center', 32 'transition-all duration-300', 33 'border-border border-y border-r first:border-l first:rounded-l-md last:rounded-r-md', 34 'group-hover:border-accent-foreground/20 group-focus-within:border-accent-foreground/20', 35 'outline outline-0 outline-accent-foreground/20', 36 { 'outline-4 outline-accent-foreground': props.isActive }, 37 )} 38 > 39 <div className="group-has-[input[data-input-otp-placeholder-shown]]:opacity-20"> 40 {props.char ?? props.placeholderChar} 41 </div> 42 {props.hasFakeCaret && <FakeCaret />} 43 </div> 44 ) 45} 46 47// You can emulate a fake textbox caret! 48function FakeCaret() { 49 return ( 50 <div className="absolute pointer-events-none inset-0 flex items-center justify-center animate-caret-blink"> 51 <div className="w-px h-8 bg-white" /> 52 </div> 53 ) 54} 55 56// Inspired by Stripe's MFA input. 57function FakeDash() { 58 return ( 59 <div className="flex w-10 justify-center items-center"> 60 <div className="w-3 h-1 rounded-full bg-border" /> 61 </div> 62 ) 63} 64 65// tailwind.config.ts for the blinking caret animation. 66const config = { 67 theme: { 68 extend: { 69 keyframes: { 70 'caret-blink': { 71 '0%,70%,100%': { opacity: '1' }, 72 '20%,50%': { opacity: '0' }, 73 }, 74 }, 75 animation: { 76 'caret-blink': 'caret-blink 1.2s ease-out infinite', 77 }, 78 }, 79 }, 80} 81 82// Small utility to merge class names. 83import { clsx } from 'clsx' 84import { twMerge } from 'tailwind-merge' 85 86import type { ClassValue } from 'clsx' 87 88export function cn(...inputs: ClassValue[]) { 89 return twMerge(clsx(inputs)) 90}
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).
This is the most complete OTP input on the web. It's fully featured
https://github.com/guilhermerodz/input-otp/assets/10366880/bdbdc96a-23da-4e89-bff8-990e6a1c4c23
By default, this input uses autocomplete='one-time-code'
and it works as it's a single input.
https://github.com/guilhermerodz/input-otp/assets/10366880/5705dac6-9159-443b-9c27-b52e93c60ea8
Stripe was my first inspiration to build this library.
Take a look at Stripe's input. The screen reader does not behave like it normally should on a normal single input. That's because Stripe's solution is to render a 1-digit input with "clone-divs" rendering a single char per div.
https://github.com/guilhermerodz/input-otp/assets/10366880/3d127aef-147c-4f28-9f6c-57a357a802d0
So we're rendering a single input with invisible/transparent colors instead. The screen reader now gets to read it, but there is no appearance. Feel free to build whatever UI you want:
https://github.com/guilhermerodz/input-otp/assets/10366880/718710f0-2198-418c-8fa0-46c05ae5475d
Should be able to support all keybindings of a common text input as it's an input.
https://github.com/guilhermerodz/input-otp/assets/10366880/185985c0-af64-48eb-92f9-2e59be9eb78f
For password managers such as LastPass, 1Password, Dashlane or Bitwarden, input-otp
will automatically detect them in the page and increase input width by ~40px to trick the password manager's browser extension and prevent the badge from rendering to the last/right slot of the input.
pushPasswordManagerStrategy="none"
.https://github.com/guilhermerodz/input-otp/assets/10366880/bf01af88-1f82-463e-adf4-54a737a92f59
The root container. Define settings for the input via props. Then, use the render
prop to create the slots.
1type OTPInputProps = { 2 // The number of slots 3 maxLength: number 4 5 // Render function creating the slots 6 render: (props: RenderProps) => React.ReactElement 7 // PS: Render prop is mandatory, except in cases 8 // you'd like to consume the original Context API. 9 // (search for Context in this docs) 10 11 // The class name for the root container 12 containerClassName?: string 13 14 // Value state controlling the input 15 value?: string 16 // Setter for the controlled value (or callback for uncontrolled value) 17 onChange?: (newValue: string) => unknown 18 19 // Callback when the input is complete 20 onComplete?: (...args: any[]) => unknown 21 22 // Where is the text located within the input 23 // Affects click-holding or long-press behavior 24 // Default: 'left' 25 textAlign?: 'left' | 'center' | 'right' 26 27 // Virtual keyboard appearance on mobile 28 // Default: 'numeric' 29 inputMode?: 'numeric' | 'text' | 'decimal' | 'tel' | 'search' | 'email' | 'url' 30 31 // Pro tip: input-otp export some patterns by default such as REGEXP_ONLY_DIGITS which you can import from the same library path 32 // Example: import { REGEXP_ONLY_DIGITS } from 'input-otp'; 33 // Then use it as: <OTPInput pattern={REGEXP_ONLY_DIGITS}> 34 pattern?: string 35 36 // While rendering the input slot, you can access both the char and the placeholder, if there's one and it's active. 37 placeholder?: string 38 39 // Transfomer function that allows pasting, for example, "XXX-XXX" even though the input's regex/pattern doesn't allow hyphen and its max length is 6. 40 // Example: (pasted) => pasted.replaceAll('-', '') 41 pasteTransformer?: (pastedText: string) => string 42 43 // Enabled by default, it's an optional 44 // strategy for detecting Password Managers 45 // in the page and then shifting their 46 // badges to the right side, outside the input. 47 pushPasswordManagerStrategy?: 48 | 'increase-width' 49 | 'none' 50 51 // Enabled by default, it's an optional 52 // fallback for pages without JS. 53 // This is a CSS string. Write your own 54 // rules that will be applied as soon as 55 // <noscript> is parsed for no-js pages. 56 // Use `null` to disable any no-js fallback (not recommended). 57 // Default: ` 58 // [data-input-otp] { 59 // --nojs-bg: white !important; 60 // --nojs-fg: black !important; 61 // 62 // background-color: var(--nojs-bg) !important; 63 // color: var(--nojs-fg) !important; 64 // caret-color: var(--nojs-fg) !important; 65 // letter-spacing: .25em !important; 66 // text-align: center !important; 67 // border: 1px solid var(--nojs-fg) !important; 68 // border-radius: 4px !important; 69 // width: 100% !important; 70 // } 71 // @media (prefers-color-scheme: dark) { 72 // [data-input-otp] { 73 // --nojs-bg: black !important; 74 // --nojs-fg: white !important; 75 // } 76 // }` 77 noScriptCSSFallback?: string | null 78}
1export default function Page() { 2 const formRef = useRef<HTMLFormElement>(null) 3 const buttonRef = useRef<HTMLButtonElement>(null) 4 5 return ( 6 <form ref={formRef}> 7 <OTPInput 8 // ... automatically submit the form 9 onComplete={() => formRef.current?.submit()} 10 // ... or focus the button like as you wish 11 onComplete={() => buttonRef.current?.focus()} 12 /> 13 14 <button ref={buttonRef}>Submit</button> 15 </form> 16 ) 17}
1export default function Page() { 2 return ( 3 <form ref={formRef}> 4 <OTPInput 5 autoFocus 6 // Pro tip: accepts all common HTML input props... 7 /> 8 </form> 9 ) 10}
1const { register, handleSubmit } = useForm(); 2// Then register it like a text input 3<InputOTP {...register("otp")} />
You can also use react-hook-form's Controller if needed:
1const { control } = useForm(); 2// Then control it like a text input 3<Controller 4 name="customOTP" 5 control={control} 6 defaultValue="" 7 render={({ field }) => ( 8 <OTPInput 9 {...field} 10 label="Custom OTP" 11 /> 12 )} 13/>
By default, input-otp
handles password managers for you.
The password manager badges should be automatically shifted to the right side.
However, if you still want to block password managers, please disable the pushPasswordManagerStrategy
and then manually block each PWM.
1<OTPInput 2 // First, disable library's built-in strategy 3 // for shifting badges automatically 4- pushPasswordManagerStrategy="increase-width" 5+ pushPasswordManagerStrategy="none" 6 // Then, manually add specifics attributes 7 // your password manager docs 8 // Example: block LastPass 9+ data-lpignore="true" 10 // Example: block 1Password 11+ data-1p-ignore="true" 12/>
By default, input-otp
handles cases where JS is not in the page by applying custom CSS styles.
If you do not like the fallback design and want to apply it to your own, just pass a prop:
1// This is the default CSS fallback. 2// Feel free to change it entirely and apply to your design system. 3const NOSCRIPT_CSS_FALLBACK = ` 4[data-input-otp] { 5 --nojs-bg: white !important; 6 --nojs-fg: black !important; 7 8 background-color: var(--nojs-bg) !important; 9 color: var(--nojs-fg) !important; 10 caret-color: var(--nojs-fg) !important; 11 letter-spacing: .25em !important; 12 text-align: center !important; 13 border: 1px solid var(--nojs-fg) !important; 14 border-radius: 4px !important; 15 width: 100% !important; 16} 17@media (prefers-color-scheme: dark) { 18 [data-input-otp] { 19 --nojs-bg: black !important; 20 --nojs-fg: white !important; 21 } 22}` 23 24<OTPInput 25 // Pass your own custom styles for when JS is disabled 26+ noScriptCSSFallback={NOSCRIPT_CSS_FALLBACK} 27/>
1<OTPInput 2 // Add class to the input itself 3+ className="focus-visible:ring-0" 4 // Not the container 5 containerClassName="..." 6/>
1<OTPInput 2 // customizable but not recommended 3+ textAlign="center" 4/>
NOTE: this also affects the selected caret position after a touch/click.
textAlign="left"
textAlign="center"
textAlign="right"
1+import { OTPInputContext } from 'input-otp' 2 3function MyForm() { 4 return ( 5 <OTPInput 6- // First remove the `render` prop 7- render={...} 8 > 9 <OTPInputWrapper /> 10 </OTPInput> 11 ) 12} 13 14+function OTPInputWrapper() { 15+ const inputContext = React.useContext(OTPInputContext) 16+ return ( 17+ <> 18+ {inputContext.slots.map((slot, idx) => ( 19+ <Slot key={idx} {...slot} /> 20+ ))} 21+ </> 22+ ) 23+}
NOTE: this also affects the selected caret position after a touch/click.
textAlign="left"
textAlign="center"
textAlign="right"
Add the following setting to your .vscode/settings.json
:
1{ 2 "tailwindCSS.classAttributes": [ 3 "class", 4 "className", 5+ ".*ClassName" 6 ] 7}
No vulnerabilities found.
No security vulnerabilities found.