NumberInput 数字输入框

输入数值的输入框。

<script setup lang="ts">
  import { NumberInput } from '@hina-ui/vue'
</script>

<template>
  <NumberInput :default-value="3" :min="0" :max="10" aria-label="数量" class="w-40" />
</template>
vue

用法

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

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

<script setup lang="ts">
  import { ref } from 'vue'
  import { NumberInput } from '@hina-ui/vue'

  const quantity = ref(1)
</script>

<template>
  <NumberInput v-model="quantity" aria-label="数量" placeholder="数量" class="w-40" />
</template>
vue

示例

范围与步长

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

<script setup lang="ts">
  import { ref } from 'vue'
  import { NumberInput, Stack } from '@hina-ui/vue'

  const rating = ref(2.5)
  const pages = ref(12)
</script>

<template>
  <Stack class="w-40">
    <NumberInput v-model="rating" :min="0" :max="10" :step="0.5" aria-label="评分" />
    <NumberInput v-model="pages" :min="1" :step="5" :step-snapping="false" aria-label="页数" />
  </Stack>
</template>
vue

格式

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

<script setup lang="ts">
  import { ref } from 'vue'
  import { NumberInput, Stack } from '@hina-ui/vue'

  const price = ref(1280)
  const discount = ref(0.35)
  const euro = ref(1234.5)
</script>

<template>
  <Stack class="w-48">
    <NumberInput
      v-model="price"
      :format-options="{ style: 'currency', currency: 'CNY' }"
      aria-label="价格"
    />
    <NumberInput
      v-model="discount"
      :step="0.01"
      :min="0"
      :max="1"
      :format-options="{ style: 'percent' }"
      aria-label="折扣"
    />
    <NumberInput
      v-model="euro"
      locale="de-DE"
      :format-options="{ style: 'currency', currency: 'EUR' }"
      aria-label="欧元价格"
    />
  </Stack>
</template>
vue

尺寸

三档尺寸与输入框相同。

<script setup lang="ts">
  import { NumberInput, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack class="w-40">
    <NumberInput size="sm" :default-value="1" aria-label="小号" />
    <NumberInput size="md" :default-value="1" aria-label="中号" />
    <NumberInput size="lg" :default-value="1" aria-label="大号" />
  </Stack>
</template>
vue

形态

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

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

<script setup lang="ts">
  import { NumberInput, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack class="w-full max-w-xs">
    <NumberInput variant="primary" aria-label="primary" :default-value="1" />
    <NumberInput variant="secondary" aria-label="secondary" :default-value="1" />
    <NumberInput variant="bare" aria-label="bare" :default-value="1" />
  </Stack>
</template>
vue

状态

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

<script setup lang="ts">
  import { NumberInput, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack class="w-40">
    <NumberInput invalid :default-value="120" :max="99" aria-label="校验未通过" />
    <NumberInput disabled :default-value="3" aria-label="已禁用" />
    <NumberInput readonly :default-value="3" aria-label="只读" />
  </Stack>
</template>
vue

不带步进按钮

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

<script setup lang="ts">
  import { NumberInput } from '@hina-ui/vue'
</script>

<template>
  <NumberInput :controls="false" :min="1" placeholder="页码" aria-label="页码" class="w-40" />
</template>
vue

在表单中

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

1 到 9999 元

<script setup lang="ts">
  import { reactive, ref } from 'vue'
  import * as v from 'valibot'
  import { Button, Form, FormField, NumberInput, Text } from '@hina-ui/vue'

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

  const values = reactive({ price: null as number | null })
  const saved = ref('')

  async function save(data: unknown) {
    await new Promise(resolve => setTimeout(resolve, 600))
    saved.value = JSON.stringify(data)
  }
</script>

<template>
  <Form v-slot="{ submitting }" :values="values" :rules="schema" class="w-80" @submit="save">
    <FormField name="price" label="价格" description="1 到 9999 元" required>
      <NumberInput v-model="values.price" :min="0" :step="1" />
    </FormField>
    <Button type="submit" :loading="submitting" class="self-start">上架</Button>
    <Text v-if="saved" tone="muted" size="sm">已上架:{{ saved }}</Text>
  </Form>
</template>
vue

行为

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

无障碍

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

API

Props

属性
类型
默认值
说明
modelValue
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
是否只读
class
string
—
追加至根元素的类名
事件
参数
说明
update:modelValue
value: number | undefined
数值变化