Skip to content

验证码输入 OTP

输入短信 / 邮件验证码、激活码。每个字符一格显示,但实际是一个透明的原生输入框覆盖在格子上:粘贴整段、iOS / Android 自动填充验证码、读屏、表单提交都和普通输入框一样工作。

examples/otp/basic.html

试一试

  • 直接粘贴 123 456123-456,非数字字符会被去掉
  • 按退格逐位删除;光标始终在末尾

状态与变体

examples/otp/variants.html
  • 错误:给输入框加 aria-invalid="true",所有格子描边变红
  • 禁用:给输入框加 disabled
  • 字母数字:根元素加 data-type="alphanumeric",自动转大写;可以在格子之间插入分隔符(aria-hidden<span>
  • 长度:由输入框的 maxlength 决定,格子数量与之一致;紧凑尺寸把格子 size-12 text-xl 换成 size-10 text-lg

完整的验证流程

examples/otp/verify.html

输满 6 位自动提交(data-autosubmit),验证失败后清空并重新聚焦;「重新发送」使用倒计时按钮。

结构

html
<label for="code" class="text-sm font-medium">邮箱验证码</label>
<div class="group relative flex w-fit gap-2" data-cb-otp>
  <input id="code" type="text" data-cb-otp-input name="code" maxlength="6"
         inputmode="numeric" autocomplete="one-time-code" required
         class="absolute inset-0 z-10 h-full w-full bg-transparent text-transparent caret-transparent outline-none …" />
  <div data-cb-otp-slot aria-hidden="true"
       class="flex size-12 items-center justify-center rounded-lg border border-input font-mono text-xl
              data-active:border-brand data-active:ring-3 data-active:ring-ring/20
              group-has-[input[aria-invalid=true]]:border-destructive"></div>
  <!-- 共 maxlength 个 slot -->
</div>
属性 / 方法说明
maxlength(写在 input 上)验证码长度
data-type="alphanumeric"允许字母,默认只允许数字
data-autosubmit输满后调用所在表单的 requestSubmit()
cb:otp-change每次变化,detail = { value }
cb:otp-complete输满时,detail = { value }
CbtisUI.setOtp(el, '')设置或清空

格子的状态由脚本写入的属性表达:当前格 data-active,已填写 data-filled,当前空格内会插入一个闪烁的假光标。

使用建议

推荐

  • 必须保留 autocomplete="one-time-code":手机键盘会直接提示短信里的验证码
  • 输入框上方写清楚验证码发到了哪里(「已发送至 name@company.com」)
  • 输满自动提交时,提交按钮仍然保留,给自动提交失败或关闭了脚本的情况兜底
  • 验证失败后清空并聚焦,错误文案告诉用户下一步(重新输入 / 重新获取)

避免

  • 用 6 个独立的 <input> 实现:粘贴、自动填充、读屏体验都会变差
  • 禁止粘贴

无障碍

  • 读屏只会读到一个输入框(标签 + 提示 + 已输入的内容),格子全部 aria-hidden
  • 标签可以视觉隐藏(sr-only),但不能省略
  • 输入框字号为 16px,iOS 聚焦时不会自动放大页面

与 v2 的差异

v2 现状规范
EmailVerifyDialogResumeApplicationDialog 使用普通输入框,前端只做 6 位数字校验验证码输入组件,输满自动提交