NumberInput 数字输入框

输入数值的输入框。

import { NumberInput } from '@hina-ui/react'

export default function Demo() {
  return <NumberInput defaultValue={3} min={0} max={10} aria-label="数量" className="w-40" />
}
tsx

用法

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

数值输入框由输入区与一组步进按钮组成,value / onValueChange 绑定数值。方向键、Page Up 与 Page Down、Home 与 End 以及聚焦时的滚轮都能调整数值。未声明的属性都会传给内部的 input。

import { NumberInput } from '@hina-ui/react'

export default function Demo() {
  return <NumberInput defaultValue={1} aria-label="数量" placeholder="数量" className="w-40" />
}
tsx

示例

范围与步长

min 与 max 限定范围,step 决定每次增减的幅度。默认把输入的值对齐到步长,关闭 stepSnapping 后只限制在范围内。

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

export default function Demo() {
  return (
    <Stack className="w-40">
      <NumberInput defaultValue={2.5} min={0} max={10} step={0.5} aria-label="评分" />
      <NumberInput defaultValue={12} min={1} step={5} stepSnapping={false} aria-label="页数" />
    </Stack>
  )
}
tsx

格式

formatOptions 接受 Intl.NumberFormat 的选项,货币、百分比与单位都能显示。locale 决定分隔符与符号的写法,未设置时跟随语言包的语言。

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

export default function Demo() {
  return (
    <Stack className="w-48">
      <NumberInput
        defaultValue={1280}
        formatOptions={{ style: 'currency', currency: 'CNY' }}
        aria-label="价格"
      />
      <NumberInput
        defaultValue={0.35}
        step={0.01}
        min={0}
        max={1}
        formatOptions={{ style: 'percent' }}
        aria-label="折扣"
      />
      <NumberInput
        defaultValue={1234.5}
        locale="de-DE"
        formatOptions={{ style: 'currency', currency: 'EUR' }}
        aria-label="欧元价格"
      />
    </Stack>
  )
}
tsx

尺寸

三档尺寸与输入框相同。

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

export default function Demo() {
  return (
    <Stack className="w-40">
      <NumberInput size="sm" defaultValue={1} aria-label="小号" />
      <NumberInput size="md" defaultValue={1} aria-label="中号" />
      <NumberInput size="lg" defaultValue={1} aria-label="大号" />
    </Stack>
  )
}
tsx

形态

primary 带边框、背景与阴影;secondary 使用浅色背景;bare 背景透明,不绘制边框、阴影、悬停底色或容器聚焦环,尺寸与内边距仍由原有设置控制。

bare 保留禁用状态与 aria-invalid;错误信息可由 FormField 显示。步进按钮之间及其与输入区之间的分隔线也会隐藏,按钮仍可正常操作。

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

export default function Demo() {
  return (
    <Stack className="w-full max-w-xs">
      <NumberInput variant="primary" aria-label="primary" defaultValue={1} />
      <NumberInput variant="secondary" aria-label="secondary" defaultValue={1} />
      <NumberInput variant="bare" aria-label="bare" defaultValue={1} />
    </Stack>
  )
}
tsx

状态

invalid 标出校验未通过,disabled 不可编辑,readOnly 可以聚焦与复制但不能修改。

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

export default function Demo() {
  return (
    <Stack className="w-40">
      <NumberInput invalid defaultValue={120} max={99} aria-label="校验未通过" />
      <NumberInput disabled defaultValue={3} aria-label="已禁用" />
      <NumberInput readonly defaultValue={3} aria-label="只读" />
    </Stack>
  )
}
tsx

不带步进按钮

关闭 controls 后只保留输入区,键盘与滚轮仍然有效。

import { NumberInput } from '@hina-ui/react'

export default function Demo() {
  return (
    <NumberInput controls={false} min={1} placeholder="页码" aria-label="页码" className="w-40" />
  )
}
tsx

在表单中

放进 FormField 后,标签指向输入框,说明与错误信息由字段渲染;校验规则与提交交给 Form。值是数字或者 null,范围规则写在数字层。

1 到 9999 元

'use client'

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

const schema = v.object({
  price: v.pipe(
    v.number('请输入价格'),
    v.minValue(1, '价格至少 1 元'),
    v.maxValue(9999, '价格不超过 9999 元'),
  ),
})

export default function Demo() {
  const [values, setValues] = useState({ price: null as number | null | undefined })
  const [saved, setSaved] = useState('')

  async function save(data: unknown) {
    await new Promise(resolve => setTimeout(resolve, 600))
    setSaved(JSON.stringify(data))
  }

  return (
    <Form values={values} rules={schema} className="w-80" onSubmit={save}>
      {({ submitting }) => (
        <>
          <FormField name="price" label="价格" description="1 到 9999 元" required>
            <NumberInput
              value={values.price}
              onValueChange={price => setValues({ ...values, price })}
              min={0}
              step={1}
            />
          </FormField>
          <Button type="submit" loading={submitting} className="self-start">
            上架
          </Button>
          {saved && (
            <Text tone="muted" size="sm">
              已上架:{saved}
            </Text>
          )}
        </>
      )}
    </Form>
  )
}
tsx

行为

  • 失焦或者按 Enter 时解析输入。无法解析时恢复为上一个值,超出范围的值限制到边界。
  • 输入时拒绝不能构成数字的字符。
  • 步进按钮支持按住连续增减,到达边界的一侧会禁用。
  • 悬停、聚焦、错误与禁用的表现与 Input 相同。

无障碍

  • 根元素为 role="group",输入区为 role="spinbutton",带 aria-valuenow、aria-valuemin 与 aria-valuemax。
  • 步进按钮不进入 Tab 序列,键盘用户用方向键调整。按钮名称随语言包本地化。
  • 应当配合 label 元素或者 aria-label 提供名称。invalid 同时设置 aria-invalid。

API

Props

属性
类型
默认值
说明
value
number | null
—
数值
defaultValue
number
—
非受控时的初始值
min
number
—
最小值
max
number
—
最大值
step
number
1
步长
stepSnapping
boolean
true
是否把值对齐到步长
formatOptions
Intl.NumberFormatOptions
—
显示格式
locale
string
—
格式化所用的语言
controls
boolean
true
是否显示步进按钮
variant
'primary' | 'secondary' | 'bare'
'primary'
形态
size
'sm' | 'md' | 'lg'
'md'
尺寸
invalid
boolean
false
是否校验未通过
disabled
boolean
false
是否禁用
readOnly
boolean
false
是否只读
className
string
—
追加至根元素的类名
回调
参数
说明
onValueChange
value: number | undefined
数值变化