[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-73761":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":16,"stars30d":17,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":18,"rankGlobal":10,"rankLanguage":10,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":20,"hasPages":20,"topics":22,"createdAt":10,"pushedAt":10,"updatedAt":29,"readmeContent":30,"aiSummary":31,"trendingCount":16,"starSnapshotCount":16,"syncStatus":14,"lastSyncTime":32,"discoverSource":33},73761,"input-otp","guilhermerodz\u002Finput-otp","guilhermerodz","One time passcode Input. Accessible & unstyled.","https:\u002F\u002Finput-otp.rodz.dev",null,"TypeScript",3068,85,2,14,0,8,58.6,"MIT License",false,"master",[23,24,25,26,27,28],"2fa","input","mfa","otp","otp-verification","react","2026-06-12 04:01:11","# The only accessible & unstyled & full featured Input OTP component in the Web.\n\n### OTP Input for React 🔐 by [@guilhermerodz](https:\u002F\u002Ftwitter.com\u002Fguilherme_rodz)\n\n\u003Ch3 align=\"center\">Hero Sponsors 🎖️\u003C\u002Fh3>\n\u003Cp align=\"center\">\n\u003Ca href=\"https:\u002F\u002Fgo.clerk.com\u002Finput-otp\" target=\"_blank\">\n\u003Cimg alt=\"Clerk\" src='https:\u002F\u002Finput-otp.rodz.dev\u002Fsponsors\u002Fclerk-wordmark-white-in-black-bg.svg' width=\"130\" style=\"aspect-ratio: auto;\"\u002F>\n\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgo.resend.com\u002Finput-otp\" target=\"_blank\">\n\u003Cimg alt=\"Resend\" src='https:\u002F\u002Finput-otp.rodz.dev\u002Fsponsors\u002Fresend-wordmark-white-in-black-bg.svg' width=\"130\" style=\"aspect-ratio: auto;\"\u002F>\n\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fevomi.com\u002F?utm_source=github&utm_campaign=otp\" target=\"_blank\">\n\u003Cimg alt=\"Evomi\" src='https:\u002F\u002Finput-otp.rodz.dev\u002Fsponsors\u002Fevomi-wordmark-white-in-black-bg.svg' width=\"130\" style=\"aspect-ratio: auto;\"\u002F>\n\u003C\u002Fa>\n\u003C\u002Fp>\n\nhttps:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002F753751f5-eda8-4145-a4b9-7ef51ca5e453\n\n## Usage\n\n```bash\nnpm install input-otp\n```\n\nThen import the component.\n\n```diff\n+'use client'\n+import { OTPInput } from 'input-otp'\n\nfunction MyForm() {\n  return \u003Cform>\n+   \u003COTPInput maxLength={6} render={({slots})  => (...)} \u002F>\n  \u003C\u002Fform>\n}\n```\n\n## Default example\n\nThe example below uses `tailwindcss` `@shadcn\u002Fui` `tailwind-merge` `clsx`:\n\n```tsx\n'use client'\nimport { OTPInput, SlotProps } from 'input-otp'\n\u003COTPInput\n  maxLength={6}\n  containerClassName=\"group flex items-center has-[:disabled]:opacity-30\"\n  render={({ slots }) => (\n    \u003C>\n      \u003Cdiv className=\"flex\">\n        {slots.slice(0, 3).map((slot, idx) => (\n          \u003CSlot key={idx} {...slot} \u002F>\n        ))}\n      \u003C\u002Fdiv>\n\n      \u003CFakeDash \u002F>\n\n      \u003Cdiv className=\"flex\">\n        {slots.slice(3).map((slot, idx) => (\n          \u003CSlot key={idx} {...slot} \u002F>\n        ))}\n      \u003C\u002Fdiv>\n    \u003C\u002F>\n  )}\n\u002F>\n\n\u002F\u002F Feel free to copy. Uses @shadcn\u002Fui tailwind colors.\nfunction Slot(props: SlotProps) {\n  return (\n    \u003Cdiv\n      className={cn(\n        'relative w-10 h-14 text-[2rem]',\n        'flex items-center justify-center',\n        'transition-all duration-300',\n        'border-border border-y border-r first:border-l first:rounded-l-md last:rounded-r-md',\n        'group-hover:border-accent-foreground\u002F20 group-focus-within:border-accent-foreground\u002F20',\n        'outline outline-0 outline-accent-foreground\u002F20',\n        { 'outline-4 outline-accent-foreground': props.isActive },\n      )}\n    >\n      \u003Cdiv className=\"group-has-[input[data-input-otp-placeholder-shown]]:opacity-20\">\n        {props.char ?? props.placeholderChar}\n      \u003C\u002Fdiv>\n      {props.hasFakeCaret && \u003CFakeCaret \u002F>}\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F You can emulate a fake textbox caret!\nfunction FakeCaret() {\n  return (\n    \u003Cdiv className=\"absolute pointer-events-none inset-0 flex items-center justify-center animate-caret-blink\">\n      \u003Cdiv className=\"w-px h-8 bg-white\" \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F Inspired by Stripe's MFA input.\nfunction FakeDash() {\n  return (\n    \u003Cdiv className=\"flex w-10 justify-center items-center\">\n      \u003Cdiv className=\"w-3 h-1 rounded-full bg-border\" \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F tailwind.config.ts for the blinking caret animation.\nconst config = {\n  theme: {\n    extend: {\n      keyframes: {\n        'caret-blink': {\n          '0%,70%,100%': { opacity: '1' },\n          '20%,50%': { opacity: '0' },\n        },\n      },\n      animation: {\n        'caret-blink': 'caret-blink 1.2s ease-out infinite',\n      },\n    },\n  },\n}\n\n\u002F\u002F Small utility to merge class names.\nimport { clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nimport type { ClassValue } from 'clsx'\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n```\n\n## How it works\n\nThere's currently no native OTP\u002F2FA\u002FMFA input in HTML, which means people are either going with 1. a simple input design or 2. custom designs like this one.\nThis 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_).\n\n## Features\n\nThis is the most complete OTP input on the web. It's fully featured \n\n\u003Cdetails>\n\u003Csummary>Supports iOS + Android copy-paste-cut\u003C\u002Fsummary>\n\nhttps:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002Fbdbdc96a-23da-4e89-bff8-990e6a1c4c23\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>Automatic OTP code retrieval from transport (e.g SMS)\u003C\u002Fsummary>\n\nBy default, this input uses `autocomplete='one-time-code'` and it works as it's a single input. \n\nhttps:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002F5705dac6-9159-443b-9c27-b52e93c60ea8\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>Supports screen readers (a11y)\u003C\u002Fsummary>\n\nStripe was my first inspiration to build this library.\n\nTake a look at Stripe's input. The screen reader does not behave like it normally should on a normal single input.\nThat's because Stripe's solution is to render a 1-digit input with \"clone-divs\" rendering a single char per div.\n\nhttps:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002F3d127aef-147c-4f28-9f6c-57a357a802d0\n\nSo we're rendering a single input with invisible\u002Ftransparent colors instead.\nThe screen reader now gets to read it, but there is no appearance. Feel free to build whatever UI you want:\n\nhttps:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002F718710f0-2198-418c-8fa0-46c05ae5475d\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>Supports all keybindings\u003C\u002Fsummary>\n\nShould be able to support all keybindings of a common text input as it's an input.\n\nhttps:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002F185985c0-af64-48eb-92f9-2e59be9eb78f\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>Automatically optimizes for password managers\u003C\u002Fsummary>\n\n\nFor 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\u002Fright slot of the input.\n\n\u003Cimg width=\"670\" alt=\"image\" src=\"https:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002F9bb306ca-deff-4803-aa3d-148c594a540c\">\n\n- **This feature is optional and it's enabled by default. You can disable this optimization by adding `pushPasswordManagerStrategy=\"none\"`.**\n- **This feature does not cause visible layout shift.**\n\n### Auto tracks if the input has space in the right side for the badge\n\nhttps:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002Fbf01af88-1f82-463e-adf4-54a737a92f59\n\n\u003C\u002Fdetails>\n\n## API Reference\n\n### OTPInput\n\nThe root container. Define settings for the input via props. Then, use the `render` prop to create the slots.\n\n#### Props\n\n```ts\ntype OTPInputProps = {\n  \u002F\u002F The number of slots\n  maxLength: number\n\n  \u002F\u002F Render function creating the slots\n  render: (props: RenderProps) => React.ReactElement\n  \u002F\u002F PS: Render prop is mandatory, except in cases\n  \u002F\u002F you'd like to consume the original Context API.\n  \u002F\u002F (search for Context in this docs)\n\n  \u002F\u002F The class name for the root container\n  containerClassName?: string\n\n  \u002F\u002F Value state controlling the input\n  value?: string\n  \u002F\u002F Setter for the controlled value (or callback for uncontrolled value)\n  onChange?: (newValue: string) => unknown\n\n  \u002F\u002F Callback when the input is complete\n  onComplete?: (...args: any[]) => unknown\n\n  \u002F\u002F Where is the text located within the input\n  \u002F\u002F Affects click-holding or long-press behavior\n  \u002F\u002F Default: 'left'\n  textAlign?: 'left' | 'center' | 'right'\n\n  \u002F\u002F Virtual keyboard appearance on mobile\n  \u002F\u002F Default: 'numeric'\n  inputMode?: 'numeric' | 'text' | 'decimal' | 'tel' | 'search' | 'email' | 'url'\n\n  \u002F\u002F Pro tip: input-otp export some patterns by default such as REGEXP_ONLY_DIGITS which you can import from the same library path\n  \u002F\u002F Example: import { REGEXP_ONLY_DIGITS } from 'input-otp';\n  \u002F\u002F Then use it as: \u003COTPInput pattern={REGEXP_ONLY_DIGITS}>\n  pattern?: string\n\n  \u002F\u002F While rendering the input slot, you can access both the char and the placeholder, if there's one and it's active.\n  placeholder?: string\n\n  \u002F\u002F Transfomer function that allows pasting, for example, \"XXX-XXX\" even though the input's regex\u002Fpattern doesn't allow hyphen and its max length is 6.\n  \u002F\u002F Example: (pasted) => pasted.replaceAll('-', '')\n  pasteTransformer?: (pastedText: string) => string\n\n  \u002F\u002F Enabled by default, it's an optional\n  \u002F\u002F strategy for detecting Password Managers\n  \u002F\u002F in the page and then shifting their\n  \u002F\u002F badges to the right side, outside the input.\n  pushPasswordManagerStrategy?:\n    | 'increase-width'\n    | 'none'\n\n  \u002F\u002F Enabled by default, it's an optional\n  \u002F\u002F fallback for pages without JS.\n  \u002F\u002F This is a CSS string. Write your own\n  \u002F\u002F rules that will be applied as soon as\n  \u002F\u002F \u003Cnoscript> is parsed for no-js pages.\n  \u002F\u002F Use `null` to disable any no-js fallback (not recommended).\n  \u002F\u002F Default: `\n  \u002F\u002F [data-input-otp] {\n  \u002F\u002F   --nojs-bg: white !important;\n  \u002F\u002F   --nojs-fg: black !important;\n  \u002F\u002F \n  \u002F\u002F   background-color: var(--nojs-bg) !important;\n  \u002F\u002F   color: var(--nojs-fg) !important;\n  \u002F\u002F   caret-color: var(--nojs-fg) !important;\n  \u002F\u002F   letter-spacing: .25em !important;\n  \u002F\u002F   text-align: center !important;\n  \u002F\u002F   border: 1px solid var(--nojs-fg) !important;\n  \u002F\u002F   border-radius: 4px !important;\n  \u002F\u002F   width: 100% !important;\n  \u002F\u002F }\n  \u002F\u002F @media (prefers-color-scheme: dark) {\n  \u002F\u002F   [data-input-otp] {\n  \u002F\u002F     --nojs-bg: black !important;\n  \u002F\u002F     --nojs-fg: white !important;\n  \u002F\u002F   }\n  \u002F\u002F }`\n  noScriptCSSFallback?: string | null\n}\n```\n\n## Examples\n\n\u003Cdetails>\n\u003Csummary>Automatic form submission on OTP completion\u003C\u002Fsummary>\n\n```tsx\nexport default function Page() {\n  const formRef = useRef\u003CHTMLFormElement>(null)\n  const buttonRef = useRef\u003CHTMLButtonElement>(null)\n\n  return (\n    \u003Cform ref={formRef}>\n      \u003COTPInput\n        \u002F\u002F ... automatically submit the form\n        onComplete={() => formRef.current?.submit()}\n        \u002F\u002F ... or focus the button like as you wish\n        onComplete={() => buttonRef.current?.focus()}\n      \u002F>\n\n      \u003Cbutton ref={buttonRef}>Submit\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  )\n}\n```\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>Automatically focus the input when the page loads\u003C\u002Fsummary>\n\n```tsx\nexport default function Page() {\n  return (\n    \u003Cform ref={formRef}>\n      \u003COTPInput\n        autoFocus\n        \u002F\u002F Pro tip: accepts all common HTML input props...\n      \u002F>\n    \u003C\u002Fform>\n  )\n}\n```\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>Usage with react-hook-form\u003C\u002Fsummary>\nJust use it as a regular text input:\n\n```tsx\nconst { register, handleSubmit } = useForm();\n\u002F\u002F Then register it like a text input\n\u003CInputOTP {...register(\"otp\")} \u002F>\n```\n\nYou can also use react-hook-form's Controller if needed:\n```tsx\nconst { control } = useForm();\n\u002F\u002F Then control it like a text input\n\u003CController\n  name=\"customOTP\"\n  control={control}\n  defaultValue=\"\"\n  render={({ field }) => (\n    \u003COTPInput\n      {...field}\n      label=\"Custom OTP\"\n    \u002F>\n  )}\n\u002F>\n```\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>Paste-transformers\u003C\u002Fsummary>\nIf you want to allow pasting of \"XXX-XXX\" even though the input's regex\u002Fpattern doesn't allow hyphen and its max length is 6, you can use the `pasteTransformer` prop.\n\n```tsx\n\u003COTPInput\n  \u002F\u002F Transform the pasted text to parse hyphens but remove hyphens,\n  \u002F\u002F so it fits into the input's pattern and max length.\n  pasteTransformer={(pasted) => pasted.replaceAll('-', '')}\n\u002F>\n```\n\u003C\u002Fdetails>\n\n## Caveats\n\n\u003Cdetails>\n\u003Csummary>[Workaround] If you want to block specific password manager\u002Fbadges:\u003C\u002Fsummary>\n\nBy default, `input-otp` handles password managers for you.\nThe password manager badges should be automatically shifted to the right side.\n\nHowever, if you still want to block password managers, please disable the `pushPasswordManagerStrategy` and then manually block each PWM.\n\n```diff\n\u003COTPInput\n  \u002F\u002F First, disable library's built-in strategy\n  \u002F\u002F for shifting badges automatically\n- pushPasswordManagerStrategy=\"increase-width\"\n+ pushPasswordManagerStrategy=\"none\"\n  \u002F\u002F Then, manually add specifics attributes\n  \u002F\u002F your password manager docs\n  \u002F\u002F Example: block LastPass\n+ data-lpignore=\"true\" \n  \u002F\u002F Example: block 1Password\n+ data-1p-ignore=\"true\"\n\u002F>\n```\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>[Setting] If you want to customize the `noscript` CSS fallback\u003C\u002Fsummary>\n\nBy default, `input-otp` handles cases where JS is not in the page by applying custom CSS styles.\nIf you do not like the fallback design and want to apply it to your own, just pass a prop:\n\n```diff\n\u002F\u002F This is the default CSS fallback.\n\u002F\u002F Feel free to change it entirely and apply to your design system.\nconst NOSCRIPT_CSS_FALLBACK = `\n[data-input-otp] {\n  --nojs-bg: white !important;\n  --nojs-fg: black !important;\n\n  background-color: var(--nojs-bg) !important;\n  color: var(--nojs-fg) !important;\n  caret-color: var(--nojs-fg) !important;\n  letter-spacing: .25em !important;\n  text-align: center !important;\n  border: 1px solid var(--nojs-fg) !important;\n  border-radius: 4px !important;\n  width: 100% !important;\n}\n@media (prefers-color-scheme: dark) {\n  [data-input-otp] {\n    --nojs-bg: black !important;\n    --nojs-fg: white !important;\n  }\n}`\n\n\u003COTPInput\n  \u002F\u002F Pass your own custom styles for when JS is disabled\n+ noScriptCSSFallback={NOSCRIPT_CSS_FALLBACK}\n\u002F>\n```\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>[Workaround] If you're experiencing an unwanted border on input focus:\u003C\u002Fsummary>\n\n```diff\n\u003COTPInput\n  \u002F\u002F Add class to the input itself\n+ className=\"focus-visible:ring-0\"\n  \u002F\u002F Not the container\n  containerClassName=\"...\"\n\u002F>\n```\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>[Not Recommended] If you want to centralize input text\u002Fselection, use the `textAlign` prop:\u003C\u002Fsummary>\n\n```diff\n\u003COTPInput\n  \u002F\u002F customizable but not recommended\n+ textAlign=\"center\"\n\u002F>\n```\n\nNOTE: this also affects the selected caret position after a touch\u002Fclick.\n\n`textAlign=\"left\"`\n\u003Cimg src=\"https:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002F685a03df-2b69-4a36-b21c-e453f6098f79\" width=\"300\" \u002F>\n\u003Cbr>\n\n`textAlign=\"center\"`\n\u003Cimg src=\"https:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002Fe0f15b97-ceb8-40c8-96b7-fa3a8896379f\" width=\"300\" \u002F>\n\u003Cbr>\n\n`textAlign=\"right\"`\n\u003Cimg src=\"https:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002F26697579-0e8b-4dad-8b85-3a036102e951\" width=\"300\" \u002F>\n\u003Cbr>\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>If you want to use Context props:\u003C\u002Fsummary>\n\n```diff\n+import { OTPInputContext } from 'input-otp'\n\nfunction MyForm() {\n  return (\n    \u003COTPInput\n-     \u002F\u002F First remove the `render` prop\n-     render={...}\n    >\n      \u003COTPInputWrapper \u002F>\n    \u003C\u002FOTPInput>\n  )\n}\n\n+function OTPInputWrapper() {\n+ const inputContext = React.useContext(OTPInputContext)\n+ return (\n+   \u003C>\n+     {inputContext.slots.map((slot, idx) => (\n+       \u003CSlot key={idx} {...slot} \u002F>\n+     ))}\n+   \u003C\u002F>\n+ )\n+}\n```\n\nNOTE: this also affects the selected caret position after a touch\u002Fclick.\n\n`textAlign=\"left\"`\n\u003Cimg src=\"https:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002F685a03df-2b69-4a36-b21c-e453f6098f79\" width=\"300\" \u002F>\n\u003Cbr>\n\n`textAlign=\"center\"`\n\u003Cimg src=\"https:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002Fe0f15b97-ceb8-40c8-96b7-fa3a8896379f\" width=\"300\" \u002F>\n\u003Cbr>\n\n`textAlign=\"right\"`\n\u003Cimg src=\"https:\u002F\u002Fgithub.com\u002Fguilhermerodz\u002Finput-otp\u002Fassets\u002F10366880\u002F26697579-0e8b-4dad-8b85-3a036102e951\" width=\"300\" \u002F>\n\u003Cbr>\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>[DX] Add Tailwind autocomplete for `containerClassname` attribute in VS Code.\u003C\u002Fsummary>\n\nAdd the following setting to your `.vscode\u002Fsettings.json`:\n```diff\n{\n  \"tailwindCSS.classAttributes\": [\n    \"class\",\n    \"className\",\n+   \".*ClassName\"\n  ]\n}\n```\n\u003C\u002Fdetails>\n\n#### Sponsors\n\n> [Clerk](https:\u002F\u002Fgo.clerk.com\u002Finput-otp) is the easiest way to add authentication to your application.\n\n> [Resend](https:\u002F\u002Fgo.resend.com\u002Finput-otp) is email for developers.\n\n> [Evomi](https:\u002F\u002Fevomi.com\u002F?utm_source=github&utm_campaign=otp) offers Residential Proxies starting from $0.49.\n","该项目提供了一个一次性密码输入组件，适用于React应用。它具有完全可访问性和无样式设计，允许开发者根据需要自定义样式。该组件支持多种特性，如2FA、MFA和OTP验证，且易于集成到现有的项目中。适合用于需要用户进行二次身份验证的场景，例如登录、支付确认等安全敏感操作。使用TypeScript编写，确保了代码质量和类型安全。","2026-06-11 03:47:15","high_star"]