GitHub

Command Palette

Search for a command to run...

OTPInput

(A) One-liner

OTPInput is a component that receives verification code (One-Time Password) input via "slot UI." Actual input is handled by invisible TextInput, and OTPSlots are rendered on screen. It provides paste/pattern validation/completion callback (onComplete), and you can use useOTPInput hook to separate the same logic.


(B) Installation (Track A / Track B)

Common prerequisite: unistyles side-effect import in app entry file is required.

  • Track A: import '@fleet-ui/local/core/unistyles';
  • Track B: import '@fleet-ui/core/unistyles';

Track A (CLI / local install)

Track A: add OTPInput
pnpm dlx @fleet-ui/cli add OTPInput
Track A: import
import { OTPInput } from '@fleet-ui/local/components';

Track B (NPM package)

Track B: import
import { OTPInput } from '@fleet-ui/components';

(D) Core Features & Usage

D-1. Most Common Scenario (Minimal Example)

OTPInput basic usage
import { OTPInput } from '@fleet-ui/components';
 
export function Example() {
  return (
    <OTPInput
      maxLength={6}
      onComplete={(code) => console.log('complete', code)}
      accessibilityLabel="Verification code input"
    />
  );
}

D-2. Pattern Validation: pattern

When pattern is provided, updates are ignored if input value doesn't match the regex.

Digits only
import { OTPInput } from '@fleet-ui/components';
 
export function Example() {
  return <OTPInput maxLength={6} pattern="^\\d*$" />;
}

D-3. Paste Handling: pasteTransformer

When "multiple characters at once" come in from input event, it's considered a paste and transformed via pasteTransformer before applying. Default transformer extracts maxLength digits from pasted text.

D-4. Custom Render: render

When render is provided, instead of default OTPSlot, you can draw custom UI receiving slots render info.


(E) Internal State / Shared Value / Animation

E-1. State Model (useOTPInput)

Core states managed inside the hook are:

  • value: Current OTP string
  • prevValue: Previous frame value (for slot animation trigger)
  • isFocused: Overall focus state

Input processing:

  • Input is truncated to maxLength and stored.
  • If pattern exists, checks regex pass.
  • When length becomes maxLength, calls onComplete(value).

Slot creation:

  • slots[index] contains char/prevChar/placeholderChar/isActive/hasFakeCaret, etc.

E-2. Reanimated / Shared Value

OTPSlot uses these animations:

  • Typing bounce (on new character input): scale + translateY sequence
  • Focus display:
    • bordered/faded uses borderWidth 1↔2
    • underlined uses underlineScale 0↔1
  • Color transition: Transition background/border color with spring (error color on error)

OTPCaret implements blinking with withRepeat(withTiming).


(F) Accessibility

Accessibility is structured to focus on "invisible actual TextInput."

  • Slot container (Pressable) is hidden with accessible={false}
  • Actual TextInput is accessible with:
    • accessibilityRole="text"
    • accessibilityState={{ disabled }}
    • Default label: OTP input with ${maxLength} digits
    • Default hint: Enter your verification code

On iOS, uses opacity: 0.02 instead of opacity: 0 to hide for paste functionality (caret is hidden).


(G) Props Table

Reference: Public types from packages/components/src/OTPInput/OTPInput.types.ts.

OTPInput — OTPInputProps

PropTypeRequiredDefaultDescriptionPlatform notes
maxLengthnumberYes-OTP length (slot count)
defaultValuestringNo''Initial value
onChangeText(value: string) => voidNo-Value change callback
onComplete(value: string) => voidNo-All input complete callback
patternstring | RegExpNo-Input validation regexIgnores update on mismatch
placeholderstringNo-Per-slot placeholder string
pasteTransformer(pasted: string) => stringNo-Paste transform functionDefault transformer provided
colorScheme'primary' | 'neutral' | 'error' | 'success' | 'warning' | 'info'No'neutral'Slot focus/color scheme
variant'flat' | 'bordered' | 'underlined' | 'faded'No'bordered'Slot style variant
size'sm' | 'md' | 'lg' | 'xl'No'md'Slot size/spacing/typo
rounded'none' | 'sm' | 'md' | 'lg' | 'full'No'md'Rounded
shadow'none' | 'sm' | 'md' | 'lg'No'none'Shadow
isDisabledbooleanNofalseDisabled
isInvalidbooleanNofalseError stateSlot color transition
gapnumberNo-Gap between slots (theme.spacing index)Can be auto-determined by size
containerStyleStyleProp<ViewStyle>No-Root container style
slotStyleStyleProp<ViewStyle>No-Slot style override
slotTextStyleStyleProp<TextStyle>No-Slot text style
caretStyleStyleProp<ViewStyle>No-Caret style
render(props: OTPRenderProps) => ReactNodeNo-Custom render functionReplaces default OTPSlot

TextInputProps Inheritance (Summary)

OTPInputProps extends TextInputProps, but value, onChangeText, maxLength, style are controlled by component. accessibilityLabel/accessibilityHint, inputMode, keyboardType, secureTextEntry, etc. can be passed as-is.

Hook: useOTPInput

useOTPInput(options) returns inputRef/value/isFocused/renderProps/handlers/actions, enabling reuse of same input logic in custom UI.