PinInput 验证码输入框

逐格输入验证码或者 PIN 码。

'use client'

import { useState } from 'react'
import { PinInput } from '@hina-ui/react'

export default function Demo() {
  const [code, setCode] = useState('')

  return <PinInput value={code} onValueChange={setCode} type="number" otp aria-label="验证码" />
}
tsx

用法

import { PinInput } from '@hina-ui/react'
ts

验证码输入框把一段固定长度的字符拆成若干格,每格只容纳一个字符。value / onValueChange 绑定的是完整的字符串,length 决定格数,默认六格。输入一个字符后焦点自动进入下一格,退格回到上一格,粘贴整段内容时按位分配。未声明的属性都会传给根元素,请用 aria-label 或者 aria-labelledby 为整组命名。

当前:空

'use client'

import { useState } from 'react'
import { PinInput, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  const [code, setCode] = useState('')
  const [submitted, setSubmitted] = useState('')

  return (
    <Stack gap="sm" align="start">
      <PinInput
        value={code}
        onValueChange={setCode}
        length={4}
        aria-label="邀请码"
        onComplete={setSubmitted}
      />
      <Text tone="muted" size="sm">
        {submitted ? `已填满:${submitted}` : `当前:${code || '空'}`}
      </Text>
    </Stack>
  )
}
tsx

示例

数字与一次性验证码

type="number" 只接受数字。otp 把输入框的自动填充设为一次性验证码,浏览器与系统可以把收到的短信验证码直接填入。

'use client'

import { useState } from 'react'
import { PinInput } from '@hina-ui/react'

export default function Demo() {
  const [code, setCode] = useState('')

  return <PinInput value={code} onValueChange={setCode} type="number" otp aria-label="短信验证码" />
}
tsx

遮蔽

mask 把每一格变成密码输入,适合 PIN 码。

'use client'

import { useState } from 'react'
import { PinInput } from '@hina-ui/react'

export default function Demo() {
  const [pin, setPin] = useState('')

  return (
    <PinInput
      value={pin}
      onValueChange={setPin}
      length={4}
      type="number"
      mask
      aria-label="PIN 码"
    />
  )
}
tsx

占位符

placeholder 显示在空格子里,聚焦时隐去。

'use client'

import { useState } from 'react'
import { PinInput } from '@hina-ui/react'

export default function Demo() {
  const [code, setCode] = useState('')

  return <PinInput value={code} onValueChange={setCode} placeholder="○" aria-label="验证码" />
}
tsx

尺寸

size 有 sm、md、lg 三档,每一格是边长与同档输入框高度相等的正方形。

import { PinInput, Stack } from '@hina-ui/react'

export default function Demo() {
  return (
    <Stack gap="sm" align="start">
      <PinInput size="sm" length={4} value="2048" aria-label="小号" />
      <PinInput size="md" length={4} value="2048" aria-label="中号" />
      <PinInput size="lg" length={4} value="2048" aria-label="大号" />
    </Stack>
  )
}
tsx

状态

invalid 给每一格加上警示色边框,disabled 禁用整组。variant="secondary" 是放在 surface 之内的扁平形态。

import { PinInput, Stack } from '@hina-ui/react'

export default function Demo() {
  return (
    <Stack gap="sm" align="start">
      <PinInput length={4} value="1234" invalid aria-label="校验未通过" />
      <PinInput length={4} value="1234" disabled aria-label="已禁用" />
      <PinInput length={4} value="1234" variant="secondary" aria-label="扁平形态" />
    </Stack>
  )
}
tsx

在表单中

放进 FormField 后,标签通过 aria-labelledby 关联到整组格子,说明与错误信息由字段渲染;校验规则与提交交给 Form。

已发送到你的邮箱

'use client'

import { useState } from 'react'
import * as v from 'valibot'
import { Button, Form, FormField, PinInput, Text } from '@hina-ui/react'

const schema = v.object({
  code: v.pipe(v.string('请输入验证码'), v.length(6, '验证码是 6 位数字')),
})

export default function Demo() {
  const [values, setValues] = useState({ code: '' })
  const [saved, setSaved] = useState(false)

  async function save() {
    await new Promise(resolve => setTimeout(resolve, 600))
    setSaved(true)
  }

  return (
    <Form values={values} rules={schema} className="w-80" onSubmit={save}>
      {({ submitting }) => (
        <>
          <FormField name="code" label="验证码" description="已发送到你的邮箱" required>
            <PinInput
              value={values.code}
              onValueChange={code => setValues({ ...values, code })}
              length={6}
              type="number"
              otp
            />
          </FormField>
          <Button type="submit" loading={submitting} className="self-start">
            验证
          </Button>
          {saved && (
            <Text tone="muted" size="sm">
              验证通过。
            </Text>
          )}
        </>
      )}
    </Form>
  )
}
tsx

行为

  • 每格只容纳一个字符,输入后焦点前进;在空格子里退格会回到上一格并清除它。
  • 粘贴时从当前格开始按位分配,超出格数的部分丢弃。
  • 所有格子填满时调用 onComplete,参数是完整的字符串。
  • 左右方向键在格子之间移动焦点。

无障碍

  • 根元素是 role="group",通过 aria-label 或者 aria-labelledby 命名。
  • 每一格自带「第 n 位,共 N 位」的名称,取自语言包。
  • invalid 会同时在每一格设置 aria-invalid。

API

Props

属性
类型
默认值
说明
value
string
''
完整的值
length
number
6
格数
type
'text' | 'number'
'text'
接受的字符类型
mask
boolean
false
是否以密码方式显示
otp
boolean
false
是否接收一次性验证码的自动填充
placeholder
string
''
空格子里显示的占位符
name
string
—
表单字段名,提交时携带完整的值
variant
'primary' | 'secondary'
'primary'
形态
size
'sm' | 'md' | 'lg'
'md'
尺寸
disabled
boolean
false
是否禁用
invalid
boolean
false
是否处于校验未通过状态
className
string
—
追加至根元素的类名

回调

回调
参数
说明
onValueChange
value: string
值变化时
onComplete
value: string
所有格子填满时