Assist_Design/docs/plans/2026-03-06-otp-input-redesign-design.md
barsa 1610e436a5 feat: integrate input-otp library and enhance OTP input handling
- Added input-otp library to streamline OTP input functionality.
- Refactored OtpInput component to utilize InputOTP for improved user experience and mobile SMS autofill.
- Enhanced LoginOtpStep and VerificationStep components to handle OTP input errors and clear states effectively.
- Updated global styles to include animations for OTP caret, improving visual feedback during input.
- Made minor adjustments to LoginForm and OtpStep components for better error handling and user interaction.
2026-03-06 18:56:16 +09:00

2.6 KiB

OTP Input Redesign: Replace Custom with shadcn InputOTP

Date: 2026-03-06 Status: Approved

Problem

The custom OtpInput component (components/molecules/OtpInput/OtpInput.tsx) has fundamental issues:

  • onComplete never fired on typing due to a JS bug (!string.includes("") is always false)
  • Manual focus management across 6 separate <input> elements is fragile
  • No mobile SMS autofill support
  • No password manager detection
  • Edge cases around paste, backspace, and focus are hard to maintain

Decision

Replace the custom implementation with input-otp via shadcn/ui's InputOTP component (Approach A).

Architecture

input-otp (npm)
  -> components/ui/input-otp.tsx (shadcn primitives)
    -> components/molecules/OtpInput/OtpInput.tsx (wrapper, same API)
      -> LoginOtpStep, OtpStep, VerificationStep (no changes)

Component API (unchanged)

interface OtpInputProps {
  length?: number; // default 6
  value: string;
  onChange: (value: string) => void;
  onComplete?: (value: string) => void;
  disabled?: boolean;
  error?: string;
  autoFocus?: boolean; // default true
}

Visual Design

  • Stripe-style grouped layout: two groups of 3 slots separated by a dash
  • Slots: border-border default, border-primary + ring on active, border-danger on error
  • bg-card, text-foreground, rounded corners on group edges
  • Disabled: opacity-50 cursor-not-allowed
  • Blinking caret animation

What Changes

File Action
package.json Add input-otp dependency
components/ui/input-otp.tsx New — shadcn generated primitives
components/molecules/OtpInput/OtpInput.tsx Rewrite internals, keep API
LoginOtpStep, OtpStep, VerificationStep No changes

What Gets Deleted

  • useOtpHandlers hook (focus, paste, keydown management)
  • getInputBorderClass helper
  • All 6 individual <input> elements

Error Handling

Consumer-level error handling (already implemented this session) remains unchanged:

  • LoginOtpStep: catches errors, clears input, clears error on typing via onClearError
  • OtpStep: catches errors, clears input, clears error on typing via clearOtpError
  • VerificationStep: syncs machine error to local state, clears input and error on typing

shadcn CLI Note

Generated file imports cn from @/lib/utils. Project has cn at @/shared/utils. Fix the import in the generated file after running the CLI.