InputOtp | CoUI

InputOtp

OTP 인증 코드를 입력하는 분리된 입력 필드 컴포넌트

InputOtp#

OTP(일회용 비밀번호) 인증 코드를 자리수별로 분리된 입력 필드로 입력하는 컴포넌트입니다. Flutter/Web 양쪽에서 동일한 API(CoInputOtp)를 제공합니다.

Live Preview#

Web
-
Flutter
Loading Flutter...
class InputOtpDefaultExample extends StatelessComponent {
  const InputOtpDefaultExample({super.key});

  @override
  Component build(BuildContext context) {
    return CoInputOtp(length: 6);
  }
}
class InputOtpDefaultExample extends StatelessWidget {
  const InputOtpDefaultExample({super.key});

  @override
  Widget build(BuildContext context) {
    return CoInputOtp(
      length: 6,
      onCompleted: (_) {},
    );
  }
}

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • SMS 또는 이메일로 전송된 인증 코드를 입력받을 때
  • 앱 잠금 해제를 위한 PIN 번호를 입력받을 때
  • 2단계 인증(2FA) 코드 입력 화면을 구현할 때
  • 자리수가 고정되어 있고 자동 다음 필드 이동이 필요할 때

대신 다른 컴포넌트를 사용하세요:

  • Input: 자리수가 정해지지 않은 일반 비밀번호나 코드 입력
  • Form: 여러 입력 필드를 하나의 폼으로 묶어야 할 때

기본 사용법 (Basic Usage)#

Flutter와 Web 모두 동일한 CoInputOtp 클래스를 사용합니다.

// 6자리 OTP (3자리마다 구분선)
CoInputOtp(
  length: 6,
  onCompleted: (code) => print(code),
)

// PIN 입력 (4자리, 구분선 없음, 입력값 숨김)
CoInputOtp(
  length: 4,
  obscured: true,
  separatorInterval: 0,
  onCompleted: handlePinCompleted,
)

// 초기값 복원
CoInputOtp(
  length: 6,
  initialValue: [49, 50, 51, 52, 53, 54], // "123456" codepoints
  onChanged: handleChanged,
)
// 6자리 OTP
CoInputOtp(
  length: 6,
  onCompleted: (code) => print(code),
)

// PIN 입력 (4자리, 구분선 없음, 입력값 숨김)
CoInputOtp(
  length: 4,
  obscured: true,
  separatorInterval: 0,
  onCompleted: handlePinCompleted,
)

// 초기값 복원
CoInputOtp(
  length: 6,
  initialValue: '123456',
  onChanged: handleChanged,
)

Props / Parameters#

CoInputOtp은 Flutter/Web에서 동일한 파라미터 이름을 사용합니다. 타입만 플랫폼에 맞게 다릅니다.

속성Flutter 타입Web 타입기본값설명
length int int 6 OTP 자리수
separatorInterval int int 3 N자리마다 구분선 삽입 (0 = 없음)
obscured bool bool false 입력값 숨김 여부 (PIN 모드)
enabled bool bool true 상호작용 활성화
initialValue List<int?>? String? null 초기 OTP 값
onChanged ValueChanged<List<int?>>? CoreValueChanged<String>? null 값 변경 콜백
onCompleted ValueChanged<List<int?>>? CoreValueChanged<String>? null 모든 자리 입력 완료 콜백

테마 커스터마이징 (Theme)#

CoreInputOtpTheme으로 프로젝트 수준 스타일 오버라이드가 가능합니다.

CoreComponentTheme(
  inputOtp: CoreInputOtpTheme(
    cellSize: 44,              // 기본: CoreSpace.space40 (40px)
    spacing: 12,               // 기본: CoreSpace.space8 (8px)
    height: 44,                // 기본: cellSize와 동일
    borderRadius: 8,           // 기본: CoreRadius.selector (8px)
    backgroundColor: coreColor,   // 기본: surface
    borderColor: coreColor,       // 기본: outlineVariant
    focusBackgroundColor: coreColor,
    focusBorderColor: coreColor,
    textStyle: coreTextStyle,
  ),
)

Resolve 우선순위: 위젯 파라미터 > CoreInputOtpTheme > 디자인 시스템 기본값.

동작 스펙 (Behavior)#

인터랙션#

  • 자동 이동: 숫자 입력 시 다음 칸으로 자동 포커스
  • 역방향 이동: Backspace로 현재 칸 삭제 후 이전 칸으로 포커스
  • 붙여넣기: 클립보드 전체 코드 붙여넣기 시 각 칸에 자동 분배
  • 완료 감지: 모든 자리 입력 완료 시 onCompleted 호출

상태 전환#

  • emptyfilling (첫 칸 입력 시작)
  • fillingcompleted (모든 자리 입력)
  • completedfilling (Backspace로 마지막 자리 삭제)

유효성#

  • 기본 숫자만 입력 (inputmode="numeric" / keyboardType.number)
  • enabled: false 시 모든 인터랙션 차단

사용 가이드라인 (Usage Guidelines)#

✅ Do#

PIN 입력 시 obscured: true로 보안 강화

CoInputOtp(
  length: 4,
  obscured: true,
  separatorInterval: 0,
  onCompleted: handlePinVerify,
)

PIN은 민감 정보이므로 화면 노출을 방지한다.


❌ Don't#

인증 실패 후 에러 표시 없이 기존 입력 유지

// ❌ 실패 후 코드가 남아있어 혼란 야기
CoInputOtp(
  length: 6,
  onCompleted: (otp) async {
    final ok = await authService.verify(otp);
    // 실패 시 초기화/에러 메시지 없음
  },
)

인증 실패 시 입력을 초기화하고 에러를 명확히 표시한다.

❌ Don't#

OTP 자리수를 8자리 이상으로 설정

8자리를 초과하면 모바일 레이아웃이 깨지고 입력 피로도가 급증한다. 일반적으로 4~8자리가 적절하다.

접근성 (Accessibility)#

키보드 인터랙션#

동작
0-9현재 칸에 숫자 입력 후 다음 칸 이동
Backspace현재 칸 삭제 후 이전 칸 이동
/ 이전/다음 입력 칸 수동 이동 (Flutter)
Ctrl+V클립보드 전체 붙여넣기

스크린 리더#

  • 전체 컨테이너: role="group", aria-label="One-time password input"
  • 각 칸: aria-label="Digit N of length"
  • Input: 자리수 고정이 아닌 일반 코드 입력
  • Form: OTP를 포함한 인증 폼 구성