跳到主要内容
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
开始使用WebReactSwiftUIJetpack ComposeFlutterReact Native预设你自己的设计系统与 AI 代理一起使用 Tanrei品牌设计令牌设计系统与定制Theme Studio颜色字体排印图标形状层级动效交互状态行为布局无障碍文字颜色令牌字阶令牌形状令牌层级令牌动效令牌状态令牌间距与度量令牌组件按钮主操作按钮分段按钮按钮组带菜单的按钮工具栏复制按钮复选框单选按钮开关纸片(Chip)菜单滑块滑块字段日历DateTimePicker字段组单选组复选框组下拉选择框下拉选择字段菜单栏文件上传文件选择器 进阶放置区文本输入框 进阶文本字段纸片字段字段组合框 进阶组合框字段数字输入框数字字段颜色输入框 进阶颜色字段一次性验证码输入框 进阶一次性验证码字段嵌入式文本输入框 进阶聊天输入栏卡片分隔线模态层 进阶对话框列表列表行底部面板侧边面板轮播代码块表格弹出框步骤折叠面板描述列表图示应用外壳页头树视图时间线图片预览调整手柄可调整大小的框滚动区域徽标进度指示器计量条数字滚动器加载指示器消息条(Snackbar)工具提示键盘快捷键标签(Tag)行内代码提示块(Callout)头像空状态标签组(Tag group)骨架屏文件项色块跑马灯文本标签页标签页视图应用栏导航栏导航抽屉搜索链接分页上一页/下一页链接步骤条面包屑目录链接卡片FAB 菜单状态Tanrei 是如何构建的参与贡献许可与致谢
首页组件数字输入框

数字输入框

放数字的那个方框——按所在区域设置的写法键入,或者用按键或按钮步进;数字被限制在范围之内,单位就在旁边——它自己没有标签行、帮助文本和错误文本。用于标签位于别处的数字,或者放进产品自己组合的字段里。

content_copy以 Markdown 格式查看

进阶

大多数产品直接使用包含此组件的Number field。

类别
文本输入
令牌
comp.number-input
层级
基础

概览

Web
tanrei-number-input
schedule
SwiftUI
TanreiNumberInput
radio_button_unchecked
Jetpack Compose
TanreiNumberInput
radio_button_unchecked
Flutter
TanreiNumberInput
radio_button_unchecked
React Native
NumberInput
radio_button_unchecked
不提供的变体
tanrei: filled, outlined

生成自 docs/status/status.json, specs/number-input.spec.json

单独使用:同一个行标题之下、滑块旁边的数字,以及标签位于旁边的一行里的数量

橡木书桌

数字输入框就是数字字段里的那个方框,单独拿出来:容器、数字、它的前缀和后缀,以及两个步进按钮。它上方没有标签行,下方没有帮助文本和错误文本。

用户读到的标签位于别处时,就单独使用它:

位置 用户读到的标签 输入框的 label
滑块旁边,同一个行标题之下 这一行的标题 与滑块的相同的词:“缩放比例”
表格的单元格 列标题 列和行:“数量,橡木书桌”
工具栏里的一个尺寸 旁边的字形或文本 它设置的是什么:“字号”

单独使用时,输入框需要它自己的 label。它就是无障碍名称。stacked 的输入框绝不会把它画出来;filled 或 outlined 的输入框把它画成浮在方框里的标签。

它是一个高级组件:一个不带任何附加功能的控件,供自己动手组合的产品使用。大多数表单需要的是数字字段,也就是放在字段里的这个输入框,可以直接使用。

使用它的组件是数字字段。要自己用字段框住它,就把它放进字段,由字段为它命名、描述它、把它标记为必填,并让它进入错误状态。

它是一个块,宽度与所在的位置相同,单独使用和放在字段里都是如此。它没有自己的宽度:想要窄一些的方框,就给它一个窄一些的位置。

在 Web 上它是 <tanrei-number-input>。其他平台尚未实现。

用法

value 是一个数字;输入框里没有数字时是 null。它在用户确认时改变——焦点离开、按下 Enter,或者步进——而不是每按一次键就改变。在此期间,input 带有文本当前读作的数字。

<!-- 在滑块旁边,位于产品自己绘制的行标题之下。两者各自用文字命名。 -->
<div class="row">
  <tanrei-slider id="scale" label="缩放比例" min="0.25" max="4" step="0.05" value="1.5"></tanrei-slider>
  <tanrei-number-input id="scale-number" label="缩放比例" min="0.25" max="4" step="0.05" value="1.5"
    suffix-text="×" value-text="1.5 倍" align="end" no-steppers></tanrei-number-input>
</div>

<!-- 表格的单元格:列标题就是可见的标签。 -->
<td><tanrei-number-input name="quantity-oak-desk" label="数量,橡木书桌" value="2" min="1" max="99"
  increment-label="增加" decrement-label="减少"></tanrei-number-input></td>

<script type="module">
  import '@tanrei/web/components/number-input';
  import '@tanrei/web/components/slider';

  const slider = document.querySelector('#scale');
  const number = document.querySelector('#scale-number');
  // 一个值,两个控件:各自的 input 都写进对方的 value。
  slider.addEventListener('input', () => (number.value = slider.value));
  number.addEventListener('input', (event) => {
    if (event.detail.value !== null) slider.value = event.detail.value;
  });
  // 一次确认改变了值。它已经是新的值:一个数字,或者 null。
  number.addEventListener('change', () => save(number.value));
</script>
// 进阶组件(用于构成其他组件的部件)从 advanced 入口导入。
import { NumberInput } from '@tanrei/react/advanced';

<NumberInput label="缩放比例" value={scale} min={0.25} max={4} step={0.05} suffixText="×"
  align="end" noSteppers onChange={(event) => setScale(event.currentTarget.value)} />

与滑块搭配

滑块适合凭感觉选择的值,数字输入框适合已经知道的值。把它们放在你自己的标题之下的同一行里,给两者相同的名称、范围和步长,并像上面的示例那样,把各自的 input 写进对方的 value。后缀读出来不通顺的地方,给出 valueText:× 对应“1.5 倍”。有了滑块,那两个按钮就不必要了,no-steppers 把它们去掉;方向键仍然可以步进。

手写在字段里

用字段包住一个数字输入框,就是把数字字段拆开来写。输入框自己的 label 留空:由字段为它命名。label、error、disabled 和必填设置写在字段上;name、value、范围和单位写在输入框上。

<tanrei-field label="数量" required required-text="必填"
  supporting-text="每笔订单最多 99 件。">
  <tanrei-number-input name="quantity" min="1" max="99" invalid-text="请输入数字。"
    increment-label="增加" decrement-label="减少"></tanrei-number-input>
</tanrei-field>

里面放着一个数字输入框的字段:画面与数字字段相同

只有输入框知道的事,由它告诉所在的字段。字段的帮助文本与方框里的文本对齐:无论是否绘制步进按钮,它都从方框的文本内边距处开始,也在文本内边距处结束。filled 或 outlined 的输入框自己画出字段的标签,让它浮在方框里,所以字段不绘制标签行。文本不是数字时,字段会进入错误状态,显示输入框的 invalidText;输入框没有给出时,显示字段自己的 errorText。

没有什么需要你动手去保持同步:数字输入框没有字符计数。

规格

单独使用的输入框:到达上限而被禁用的步进按钮、靠末尾边对齐的数字、没有步进按钮、错误、只读和禁用

单独使用的 filled 和 outlined:输入框自己的标签浮在方框里

以 m3-like 显示

在从右到左的布局中:用拉丁字母写的单位位于数字之后,从左到右书写;用页面自身的文字写的单位位于数字的左侧

数字和它的单位是连成一体书写的。前缀写在数字之前,后缀写在数字之后,方向按单位自身所用文字的书写方向,与页面的方向无关。所以“44 px”“$ 120 USD”和“40 %”在阿拉伯语或希伯来语的页面里,读起来与在英语页面里一样;用阿拉伯字母或希伯来字母写的单位,则位于数字的左侧。步进按钮和标签跟随页面。align="end" 让数字紧靠它的单位:单位用拉丁字母或符号书写时,在两种方向下都靠右。要让拉丁字母的货币代码位于数字的左侧,就让后缀以一个从右到左标记(U+200F)开头。

哪些属于输入框,哪些属于字段

方面 数字输入框 包着它的字段
绘制 方框、数字、占位符、前缀和后缀、两个步进按钮,以及浮在 filled 或 outlined 方框里的标签 stacked 输入框上方的标签行、必填或选填标记、帮助文本、错误文本
保存 值、范围和步长、它在表单中的位置、事件 什么都不保存
告诉辅助技术 一个微调按钮(spin button):它的值、它的范围、它的值文本 名称、帮助文本或错误文本、必填、无效、禁用

三种变体和三种尺寸——small(32)、medium(40)和 large(56)——都是文本输入框的;方框的绘制方式和尺寸,与同一变体、同一尺寸的文本输入框相同。默认系统只提供 stacked。

两个步进按钮是并排的 medium 尺寸 text 按钮。在默认系统下,它们占方框宽度中的 88,每一个都是完整的触摸目标。它们绝不会画得更小,也绝不会上下叠放:一行里放不下它们的地方,就把它们关掉。按键仍然可以步进。它们用 Button 自己的令牌绘制,绝不会处于错误状态。

方框里发生的事——值和文本、文本在某个区域设置下怎样解读,以及步进——在单独使用、放在字段里和用在数字字段里时都一样,只在数字字段的页面上写一次。

单独使用,没有字段

  • 名称是 label。labelHidden 让 filled 或 outlined 的输入框不把它画出来。
  • 错误是 error,由你来设置:方框画成错误状态,并暴露为无效。输入框没有错误文本。请在它旁边用文字说明哪里不对,或者用字段框住它。
  • 不是数字的文本会被保留,值变为空,方框画成错误状态,并触发带有 invalidText 的 entryError。invalidText 会加进输入框的描述;没有任何东西把它画出来。
  • 必填要由字段来说明。单独使用的输入框没有必填设置:表单需要回答的地方,请自己校验。

构成

部件 说明
1 container The input's box: it holds the prefix, the input, the suffix and the steppers on one line. It measures and is drawn as the text input's container of the same variant and size, carries the hover state layer and shows focus by its edge.
2 outline · 可选 The border of an outlined number input, which opens a gap where the floated label crosses it; in a stacked one, the line drawn inside the container's edge while it is focused or in error, which takes no layout space.
3 active-indicator · 可选 The line along the bottom of a filled number input; its focused state is thicker and colored.
4 label · 可选 In a filled or outlined input, its name drawn inside the box: it rests in the input line while the input is empty and not focused, and floats otherwise. A stacked input draws none: the label above it is the row of the Field that frames it.
5 required-asterisk · 可选 After the floating label's text, an asterisk, where the Field that frames the input says the control is required and marks it (NMI-7). Kept from assistive technology.
6 input The number as text, in the platform's native text element: what the person typed while they edit, the committed number in the locale's form otherwise. One line, drawn with tabular figures.
7 placeholder · 可选 A hint shown while the input is empty; never a label and never a value.
8 prefix · 可选 Fixed text before the number that is part of the field and not of the value: a currency sign.
9 suffix · 可选 Fixed text after the number that is part of the field and not of the value: the unit (px, %, kg). It is read with the value.
10 stepper · 可选 One of two buttons at the end of the container that step the number down and up by step: icon-only Buttons of the text variant showing remove and add, drawn from Button's tokens. Not focus stops; the arrow keys do their work.
11 caret The text cursor, colored by the caret part of the variant.

属性

属性 类型 默认值 说明
variant stacked | filled | outlined "stacked" The text input's three looks, so that a number input is drawn as the text inputs beside it: stacked is a filled box that shows focus and error with a line inside its edge and draws no label — the label of the Field that frames it stands above it; filled and outlined float the label inside the box. Use one style per form. A design system may leave some out (the Tanrei system ships stacked only) or add values.
size small | medium | large "medium" Minimum height of the container: stacked 32 / 40 / 56, as tall as a Button of the same size, so that the box and a Button side by side in one row stand as tall as each other; filled and outlined 40 / 48 / 56, which hold the label that floats inside them. These are the text input's three sizes, small for a bar, a toolbar or a dense row of controls. The hit area always meets the minimum target. A design system may add values.
value number null The committed number, or null (the platform's absent value) while the field holds none. It changes when the person commits — focus leaves, the action key, a step — not on every keystroke; input carries what is being typed.
label string "" Names the input for assistive technology where no Field frames it — a number under a column's head, in a toolbar, beside a slider under one row title; a filled or outlined input also draws it, as the label that floats in its box. Inside a Field leave it empty: the Field's label names the input, and is what floats.
labelHidden boolean false Does not draw the floating label of a filled or outlined input, which stays the accessible name. Inside a Field, hide the label on the Field. A stacked input draws no label, so this changes nothing there.
placeholder string "" A hint shown while the input is empty and the label is not in the input line. Never a replacement for a name.
error boolean false Draws the input in its error condition and exposes it as invalid. A Field sets it from its own error; set it here only where no Field frames the input. What a value means is the product's to validate; the input never sets this itself. A number input also enters an error of its own while the text it was left with is not a number (NMI-21).
disabled boolean false Prevents focus, editing, stepping and form submission. Inside a Field, disable the Field, so that its label follows.
readOnly boolean false The number can be focused, selected and copied, but not changed: no key steps it and no stepper is drawn.
min number null The lowest number a commit keeps; a lower one is raised to it. Null for no limit. It is also where the steps are counted from.
max number null The highest number a commit keeps; a higher one is lowered to it. Null for no limit.
step number 1 How far Up Arrow, Down Arrow and a stepper move the number; Page Up and Page Down move ten times as far. It does not limit what may be typed.
steppers boolean true Draws the two stepper buttons at the end of the field. False for a field in a row too narrow for them, or beside a slider that does their work; the keys still step.
incrementLabel string "" The accessible name of the stepper that steps up, in the product's language ("Increase"). The product's: Tanrei has no word for it. Empty reads the product's words. Required while the steppers are shown.
decrementLabel string "" The accessible name of the stepper that steps down, in the product's language ("Decrease"). The product's: Tanrei has no word for it. Empty reads the product's words. Required while the steppers are shown.
prefixText string "" Fixed text before the number, not part of the value: a currency sign ("$", "¥").
suffixText string "" Fixed text after the number, not part of the value: the unit ("px", "ms", "%", "kg"). Read to assistive technology with the number.
valueText string "" The value as words ("12 pixels", "3 stars"), given to assistive technology in place of the number with its prefix and suffix. The same text a Slider beside the field takes as its valueText.
align start | end "start" Where the number stands in the input: at the start, as the text of every other field, or at the end, for a number in a column of numbers whose digits should line up.
minFractionDigits number 0 The fewest digits shown after the decimal separator; zeros fill them (2 for an amount of money: "12.00").
maxFractionDigits number 3 The most digits kept after the decimal separator: a commit rounds to it. 0 makes the field hold whole numbers and asks for a keyboard without a separator.
locale string "" A BCP 47 language tag ("en-US", "de-DE", "ar-EG") for how the number is shown and read: its digits and its decimal separator. Empty uses the platform's current locale. The value itself is a number in every locale.
invalidText string "" The words for an input committed with a text that is not a number ("Enter a number"): given to the Field that frames it, which shows them as its error text and, while they are empty, shows its own errorText (NMI-21, FLD-28).

事件

事件 说明
input The person changed the text, or a step changed the value; carries the number the text reads as — not clamped, not rounded — or the empty value while it reads as none. Emitted for every change, not when the product sets value. What a Slider beside the field follows.
change A commit changed the value: focus left the field, the action key was pressed, a key or a stepper stepped it, or a held stepper was released; carries the value.
action The person pressed the keyboard's action key (Enter, Return, Done).
entryError The person committed a text that is not a number; carries the reason (invalid) and invalidText, the words the input has for it. The entry error itself is shown by the Field that frames the input (NMI-21).

状态

行为

以 EARS 形式写成的需求。每个平台的实现都按这些 ID 逐条测试。需求原文取自规格文件,原样以英文呈现。

ID 需求
NMI-1 THE number input SHALL draw its container at least comp.input-box.stacked.container.height.medium tall WHILE variant is stacked (or a variant a design system adds, which is drawn as a stacked box) and at least comp.input-box.container.height.medium tall WHILE variant is filled or outlined, for size medium (and so on for each size), with a stacked or outlined number input's four corners and a filled one's top corners rounded by comp.input-box.container.shape, a filled one's bottom corners by comp.input-box.container.shape-bottom, all eased by sys.shape.corner-smoothing, and SHALL lay out in it one row, centred vertically: the input line — the prefix where there is one, the input taking the remaining width and the suffix where there is one (NMI-28), in the order and the direction NMI-39 gives — and at the container's end the two steppers WHILE they are shown (NMI-25), comp.input-box.container.gap.medium after the input line (and so on). Text SHALL be comp.input-box.container.padding-inline.medium from the container's edge and the input line SHALL have at least comp.input-box.stacked.container.padding-block.medium above and below it in a stacked box and comp.input-box.container.padding-block.medium in a filled or an outlined one (and so on for each size).
NMI-2 THE number input SHALL measure as the text input of the same variant and size measures (TXI-16): the same container height, paddings and corners, and the same text style and start edge for its input text, so that a number input and a text input side by side share every horizontal line of their boxes and a number input below a text input shares its start edges. THE number input SHALL be drawn, in every state, plain and in error, with the colors the text input of the same variant has for the parts of the same name. The two are drawn from one set of tokens (NMI-47): what a layer changes of the box, it changes for both. The rows around the two boxes — the label, the help, the error — are alike for another reason: they are the one Field's (FLD-1), whichever control it frames.
NMI-7 WHERE variant is filled or outlined, WHILE the floating label is drawn (NMI-8) and the Field that frames the input gives it an asterisk as its required mark (FLD-25) THE number input SHALL draw the character "*" after the label's text, comp.input-box.requirement.asterisk-gap from it, in the label's text style and at the label's place, resting and floated with it, colored by its required-asterisk part (comp.input-box.outlined.required-asterisk.color for an outlined input; its error. part WHILE the input is in error), and SHALL keep it from assistive technology, because the required state already says it (NMI-12). It SHALL draw no tag and no optional label, as the text input of that variant draws none (TXI-50): which mark a required control gets is the Field's to say (FLD-6), and a tag named there reaches a floating label as the asterisk. WHILE no Field frames it, or the Field gives no mark, or the label is not drawn, THE number input SHALL draw no mark.
NMI-8 WHERE variant is filled or outlined and the label is drawn THE number input SHALL rest the label in the input line, in comp.input-box.label.typescale.medium for size medium (and so on), WHILE the input is empty and not focused, and otherwise float it as the text input of that variant floats its label (TXI-13): to the top of a filled container, above the input line, or onto an outlined container's top edge with a gap of comp.input-box.outline.notch-padding in the outline on each side of it, in comp.input-box.label.floated-typescale.medium for size medium (and so on); a filled number input that draws its label SHALL be at least twice comp.input-box.container.padding-block-with-label.medium plus one line of each of those two text styles tall (and so on for each size), so that it does not change height when the label floats. WHEN the label floats or rests THE number input SHALL move and resize it over comp.input-box.motion.label-duration with comp.input-box.motion.label-easing, and at once where motion is reduced. The label SHALL wrap rather than be cut and SHALL never be drawn over the input text. WHILE the input is empty THE number input SHALL show placeholder, in the placeholder color of its variant (comp.input-box.stacked.placeholder.color), only while the label is not in the input line — a stacked input, whose label is the row of its Field; floated; or not drawn — so that the two never overlap. The label it floats SHALL be its accessible name (NMI-12): the label of the Field that frames it, or its own label where none does. WHILE labelHidden is true — its own, or that Field's (FLD-25) — or it has no name THE number input SHALL draw no label. WHERE variant is stacked, or a variant laid out as stacked (NMI-43), THE number input SHALL draw no label at all: the label above it is the Field's row (FLD-2).
NMI-10 WHILE error is true — set by the product, or by the Field that frames it (FLD-8) — or the entry error of NMI-21 lasts THE number input SHALL draw every part of its container, in every state, from the error parts of its variant (comp.input-box.stacked.error.outline.color and the other error. parts), so that the box itself is marked and not only a text below it: a stacked number input draws, at rest, a line of comp.input-box.outline.width in comp.input-box.stacked.error.outline.color inside its container's edge, following its corners and taking no layout space; and it SHALL expose itself as invalid to assistive technology. The steppers SHALL stay as they are drawn: they are not in error. THE number input SHALL never set error itself — what a value means is the product's to validate — and SHALL show no error text of its own: the words that say what is wrong are the errorText of the Field that frames it, which that Field draws and gives the input as its description (FLD-7, FLD-11).
NMI-12 THE number input SHALL take its name, its description and its required state from the Field that frames it, as every control a Field frames does (FLD-10, FLD-11): the Field's label is its accessible name, whether or not any label is drawn, and the Field's supporting text with, in error, the error text after it its description. WHILE label is not empty THE number input SHALL use it as the accessible name instead, for an input no Field frames or one whose visible label is elsewhere — the head of a table's column, a toolbar, the title of a row that also holds a slider. It SHALL never use placeholder as the name, WHILE it has either name THE number input SHALL expose that one alone, over a name the product gives it on the element (aria-label or aria-labelledby); and WHILE it has neither name and the product gives it a name of its own on the element — aria-label, or aria-labelledby naming an element — THE number input SHALL expose that name as its accessible name; IF it has none of the three it SHALL still render and work, and in debug builds SHALL report that it has no accessible name. WHILE its Field says it is required THE number input SHALL expose the required state to assistive technology (and, on the web, a missing value to the form's constraint validation, NMI-35), whichever mark is drawn and where none is; it has no requirement setting of its own and SHALL NOT validate because of one.
NMI-14 WHEN the user presses a floating label, the prefix, the suffix or the container's padding THE number input SHALL move focus to its input and do nothing else: it SHALL NOT change the number or select it, and SHALL draw no press answer, because a press on a field is not an action. A press on the label row of the Field that frames it focuses the input too, and a press on that Field's supporting row does nothing: both are the Field's (FLD-12). WHERE the container is shorter than the minimum target THE number input SHALL extend its hit area vertically to sys.target.touch for touch input and sys.target.pointer for fine pointers, without changing its visual size; a platform that cannot tell the input type SHALL use sys.target.touch. THE number input SHALL add one stop to the focus order, its input, and no other: the steppers are not focus stops (NMI-25).
NMI-15 WHILE hovered or focused THE number input SHALL render its container, active indicator, outline, floating label and its asterisk, input, placeholder, prefix and suffix, caret and opacity, and the widths of its outline and active indicator, from the component tokens of the highest-precedence active state, with precedence focused over hovered (their error parts WHILE error is true); unlike a button, focus gained by pointer or touch SHALL select the focused state, because a field shows where typing will go however it was focused. THE number input SHALL show focus by its focused tokens and SHALL NOT draw the focus ring: the focused active indicator or outline is its focus indicator, and a stacked number input draws it as a line of its focused outline width, in the focused outline color of its variant, inside the container's edge, following its corners and taking no layout space, so that nothing in the container moves as the line appears, thickens or goes; WHERE sys.behavior.focus-style is glow it SHALL also draw the glow around the container while focused. A hovered stacked number input SHALL show the state layer on its container and draw no edge. Selected text in the input SHALL be highlighted in comp.input-box.selection.color and drawn in comp.input-box.selection.content.color in every variant and state. WHEN its state, error or disabled changes THE number input SHALL change its colors and widths over the state duration of its size (comp.input-box.motion.state-duration.small, .medium or .large) with comp.input-box.motion.state-easing, and at once WHERE motion is reduced.
NMI-16 THE number input SHALL hold as its value a number or nothing: value is a number, or empty (null, or the platform's own absent value) WHILE the field holds none. It SHALL edit that number as text in the platform's native text element, on one line, and WHILE the person is editing the text is theirs: THE number input SHALL NOT rewrite, reformat, clamp, round or refuse what is typed, pasted or dictated, SHALL NOT move the caret, and SHALL follow the text input's rule for an input method (TXI-4): it leaves a composition to the native element and reads the text only when the composition ends. It SHALL give the input a numeric keyboard: with a decimal separator WHILE maxFractionDigits is greater than 0, and with a minus sign WHILE min is empty or below 0, where the platform's keyboards can say so; without autocapitalization, autocorrection or suggestions.
NMI-17 WHEN a value is committed (NMI-20) or the product sets value (NMI-31) THE number input SHALL write the number into its input in the form of locale — a BCP 47 language tag, or WHILE locale is empty the platform's current locale: that locale's digits and decimal separator, a minus sign before a negative number, at least minFractionDigits and at most maxFractionDigits digits after the separator with no other trailing zeros, no exponent and no grouping separators, so that what is shown is what can be typed back. An empty value SHALL be an empty input, which shows placeholder as NMI-8 says. THE number input SHALL draw the number on one line in comp.input-box.input.typescale.medium for size medium (and so on) and the input color of its variant (comp.input-box.stacked.input.color), scrolling it sideways inside the input when it is longer than the input, and SHALL NOT cut it with an ellipsis.
NMI-18 THE number input SHALL read a text as a number leniently, by the locale's form: ignoring spaces around it and inside it; taking a hyphen-minus or a minus sign before the digits, or a plus sign, as its sign; taking ASCII digits, the locale's own digits and full-width digits as digits; taking the locale's decimal separator, once, as the separator; ignoring the locale's grouping separator wherever it stands between two digits; and ignoring prefixText before the number and suffixText after it WHERE they were typed or pasted with it ("12 px", "$5"). An empty text, or one of spaces only, SHALL read as no value. Any other text — letters, an exponent, two separators, a sign alone — SHALL read as not a number. THE number input SHALL NOT guess between two locales' forms: a character that is another locale's decimal separator and this locale's grouping separator is this locale's.
NMI-19 WHEN the person changes the text — by typing, deleting, pasting, cutting, dictating, autofill or an input method committing text — THE number input SHALL emit input once per change with the number the text now reads as (NMI-18), not clamped and not rounded, or with the empty value WHILE the text reads as no value or as not a number; WHEN a step changes the value (NMI-22) it SHALL emit input once with the new value. WHEN the product sets value THE number input SHALL NOT emit input or change. THE number input SHALL NOT change value itself while the person types: value is the committed number until the next commit.
NMI-20 WHEN the input loses focus, or the person presses the keyboard's action key (Enter, Return, Done), THE number input SHALL commit its text: WHILE the text reads as a number it SHALL take that number, raise it to min WHILE min is not empty and the number is below it, lower it to max WHILE max is not empty and the number is above it, round it to maxFractionDigits digits after the separator (a half away from zero), make it the value, and write it back into the input as NMI-17 says; WHILE the text reads as no value it SHALL make the value empty; WHILE the text reads as not a number it SHALL enter the entry error of NMI-21. It SHALL then emit change once with the value WHILE the value differs from the one last committed (the value when the input gained focus, or at the last change). THE number input SHALL clamp and round only when it commits, never while the person types, so that "25" can be typed on the way to "2.5" and "1" on the way to "100" above a minimum of 50; and it SHALL NOT move a committed number to a multiple of step: step says how far a stepper moves, not which numbers may be typed. WHEN the person presses the action key THE number input SHALL emit action after any change it causes, and on the web, inside a form, SHALL then request the form's submission as a native input does unless action was cancelled. The submission SHALL be requested through the form's default button (BTN-30): WHILE the form has a submit button — the platform's own or a Button of type submit — the first of them in document order SHALL be pressed, so that its click is emitted and its name and value are sent, and WHILE that button is disabled nothing SHALL be submitted; only a form with no submit button is asked directly.
NMI-21 WHEN the number input commits a text that reads as not a number (NMI-18, NMI-20) THE number input SHALL keep the text as the person left it, make the value empty, enter an entry error, and emit entryError once with the reason invalid and with invalidText, the words it has for the refusal. WHILE an entry error lasts THE number input SHALL be in the error condition as error puts it there (NMI-10) and SHALL report the refusal to the Field that frames it, with invalidText as its words, which that Field shows in its supporting row and announces (FLD-28: its errorText WHILE invalidText is empty; its help stays above either); WHILE no Field frames it invalidText SHALL be the input's description. THE number input SHALL end the entry error, without an event of its own, WHEN the person edits the text into a number or empties it, WHEN a step gives the field a value (NMI-22), or WHEN the product sets value, and SHALL then withdraw its report; the product's own error, and the error and the errorText of its Field, are untouched by it. THE number input SHALL NOT refuse characters as they are typed and SHALL NOT discard what it could not read, because a text that was put right is quicker than a text typed again.
NMI-22 WHEN the number input steps up or down by a count of steps — one for an arrow key and a stepper, ten for a page key (NMI-23, NMI-26) — THE number input SHALL start from the number its text reads as, or from the value WHILE the text reads as not a number, and move along the step's grid, the numbers that are min plus a whole multiple of step (zero plus a whole multiple WHILE min is empty): to the next number of the grid in that direction, so that a number between two of them goes to the nearer one in the direction of the step and not past it, and then one more number of the grid for each further step of the count; it SHALL then hold the result within min and max, compute it in decimal so that 0.1 stepped by 0.2 is 0.3, make it the value, write it into the input (NMI-17) with the caret at its end and all of it kept in view, and emit input and then change. WHILE the field holds no value a step in either direction SHALL give it the number nearest to zero that min and max allow and nothing more. A step that would not change the value — up at max, down at min — SHALL do nothing and emit nothing. WHILE step is 0 or less, or not a number, THE number input SHALL step by 1 and in debug builds report the step.
NMI-23 WHILE the input has focus, readOnly is false and no input method is composing, WHEN the person presses Up Arrow THE number input SHALL step up by step and WHEN the person presses Down Arrow step down by step (NMI-22); WHEN the person presses Page Up it SHALL step up ten steps and WHEN the person presses Page Down down ten steps: ten times step, on the grid of step and not on one of its own (NMI-22), so that with step 2 the value 42 goes to 62 and 82, as a slider's page keys (SLD-3) and the platform's own stepping of a number by ten go — a number input that stands beside a slider reaches the same numbers by the same keys. A key the platform repeats while it is held SHALL step once for each repeat. THE number input SHALL leave Home, End, Left Arrow and Right Arrow, and every arrow or page key pressed with a modifier key, to the text: they move the caret and select, as in any text input, and SHALL NOT change the value. THE number input SHALL have no key of its own for min and max.
NMI-24 THE number input SHALL NOT change its value WHEN the person turns a mouse wheel, scrolls a trackpad or swipes over it, whether or not it has focus — the page or the nearest scrolling ancestor scrolls instead — and SHALL NOT change its value by a drag along the field, its label or its unit. Only typing, the keys of NMI-23 and the steppers change the number.
NMI-25 WHILE steppers is true and readOnly is false THE number input SHALL draw two steppers at the end of its container, after the input line: first, in reading order, the one that steps down, showing the glyph remove of the system's icon set, then the one that steps up, showing the glyph add; each an icon-only Tanrei Button of the text variant and default width, at Button's small size WHILE size is small — a small box is as tall as a small Button, and a medium one would make it taller — and at Button's medium size otherwise, drawn from Button's own tokens, the two comp.number-input.stepper.gap apart, measured so that each keeps at least sys.target.touch along the row (two buttons narrower than that stand apart by what is missing), and the one nearest the end comp.number-input.stepper.inset from the container's end edge, both centred vertically in the container. Each SHALL be a Button in its press, its target and its states (BTN-5, BTN-8, BTN-9, BTN-10), with two differences: it SHALL NOT be a focus stop, because Up Arrow and Down Arrow do its work from the input and two more stops in every number of a form would cost more than they give; and it SHALL be exposed to assistive technology as a button named by decrementLabel or incrementLabel, so that a person who explores by touch can find and press it. THE number input SHALL disable the stepper that steps down WHILE the value is at or below min and the one that steps up WHILE it is at or above max, and both WHILE disabled is true. WHILE steppers is false or readOnly is true it SHALL draw neither and keep no room for them. THE number input SHALL NOT draw smaller steppers to save room, and SHALL NOT stack them: a product whose row cannot hold them sets steppers to false. WHILE incrementLabel or decrementLabel is empty THE number input SHALL still draw the stepper and in debug builds report that it has no accessible name.
NMI-26 WHEN the person presses a stepper by pointer or touch, or activates it through assistive technology, THE number input SHALL step once by step in that stepper's direction (NMI-22) when the press begins, and SHALL NOT move focus: a field that was not focused stays unfocused, so that a touch on a stepper does not raise the on-screen keyboard, and a focused input keeps its focus and its caret. A step from a stepper SHALL commit as every step does: it emits input and then change, and it ends an entry error.
NMI-27 WHILE a stepper is held down by pointer or touch THE number input SHALL repeat its step: after comp.number-input.motion.repeat-delay it SHALL step again, then again once every comp.number-input.motion.repeat-interval, and after its tenth repeated step once every comp.number-input.motion.repeat-interval-fast, until the press ends, the pointer leaves the stepper, or the value reaches min or max. A hold SHALL change how often the field steps and never how far: every step is one step. THE number input SHALL emit input for each step of a hold and change once, when the hold ends. These are waiting times, not motion: they SHALL be kept WHERE motion is reduced.
NMI-28 WHILE prefixText is not empty THE number input SHALL draw it before the number, comp.input-box.affix.gap from the input, and WHILE suffixText is not empty after the number, comp.input-box.affix.gap from the input — before and after along the direction of the number's run (NMI-39), not along the layout's — both on the input's line and in its text style, in the affix color of its variant (comp.input-box.stacked.affix.color; comp.input-box.stacked.error.affix.color WHILE error is true), exactly while the label is not in the input line (NMI-8). The prefix and the suffix are part of the field and never of the value: they cannot be edited or selected, a press on them focuses the input (NMI-14), and they SHALL NOT be cut — the input gives up its width first. THE number input SHALL read them to assistive technology with the number, as NMI-34 says, so that a unit or a currency is heard with the figure. THE number input SHALL have no control for choosing a unit: a unit the person can change is a select beside the field.
NMI-29 THE number input SHALL align the number in its input WHILE align is start to the field's start edge, as the text of every other field is — the right edge in a right-to-left layout, whatever the direction of the number's run (NMI-39) — and WHILE align is end to the end of the number's run, which is the edge of the input at which the suffix stands or would stand: for a number that stands against its unit and for a number in a column of numbers, because a number is written left to right and the digits of one place stand under each other only where the numbers end at one edge. The end of a left-to-right run is the right edge of the input in every layout direction, and the end of a right-to-left run the left edge: WHERE the run runs with the layout that is the field's end edge, and WHERE it runs against the layout — a number alone, or a number with a unit in Latin letters or signs, in a right-to-left layout — it is the field's start edge, so that there start and end are one edge. It SHALL draw the number, the placeholder, the prefix and the suffix with tabular figures where the typeface has them, so that the digits of two fields stand under each other and a number that is stepped does not move from side to side.
NMI-30 IF the product gives a min that is above max THE number input SHALL treat max as empty and in debug builds report it. IF the product sets a value below min or above max, or with more digits after the separator than maxFractionDigits, THE number input SHALL show that value as it is and SHALL NOT correct it: limits apply to what the person commits (NMI-20), and the product's own value is the product's to keep right. IF minFractionDigits is above maxFractionDigits THE number input SHALL use maxFractionDigits for both and in debug builds report it.
NMI-31 WHEN the product sets value THE number input SHALL write it into its input as NMI-17 says, replacing the text whatever the person had typed, end any entry error (NMI-21), and SHALL NOT emit input, change or entryError; a value set during an input method's composition SHALL be applied when the composition ends (TXI-4). WHEN the product changes min, max, step, minFractionDigits, maxFractionDigits or locale THE number input SHALL show its value again in the new form, enable and disable its steppers by the new limits (NMI-25), and SHALL NOT change the value or emit anything.
NMI-32 WHILE disabled is true — set by the product, or by the Field that frames it (FLD-14) — THE number input SHALL NOT be focusable or editable, SHALL NOT step by any input, SHALL NOT emit input, change, action or entryError, SHALL expose the disabled state to assistive technology, SHALL be left out of form submission on the web, SHALL disable both steppers, and SHALL render every part from the disabled component tokens of its variant (of its error parts WHILE error is true).
NMI-33 WHILE readOnly is true and disabled is false THE number input SHALL stay focusable, selectable and copyable, SHALL NOT let the person change the number by typing or by any key of NMI-23, SHALL draw no steppers (NMI-25), SHALL NOT emit input or change, SHALL expose the read-only state to assistive technology, SHALL be submitted with its form on the web, and SHALL render as an enabled field, including its hovered and focused states.
NMI-34 THE number input SHALL expose its input as a spin button (the web's spinbutton role; the platform's adjustable or stepper trait elsewhere) named as NMI-12 says: its value the committed number, its minimum min and its maximum max WHILE each is not empty, and its value text valueText WHILE that is not empty and otherwise the number as shown with prefixText before it and suffixText after it, each parted from it by a space ("$ 120 USD", "12 px", "40 %"); WHILE the field holds no value it SHALL expose no value and no value text. WHERE the platform's assistive technology can increase and decrease an adjustable element (a swipe up or down, an increment and a decrement action) those actions SHALL step by step as NMI-23 does. THE number input SHALL expose its steppers as buttons named by decrementLabel and incrementLabel with their disabled state (NMI-25), and SHALL keep the prefix and the suffix from being read a second time as texts of their own.
NMI-35 WHERE the platform has forms (the web) THE number input SHALL take part in them as a native input does: it takes name, submits its value as a decimal number with ASCII digits and a period, whatever the locale shows, or an empty string WHILE it holds no value, restores it on navigation, resets to the value it was first given, and reports to the form's constraint validation a missing value WHILE its Field says it is required (NMI-12) and the input holds none, and a bad input WHILE an entry error lasts (NMI-21).
NMI-36 THE number input SHALL be block-level, framed and bare alike: exactly as wide as the place it stands in — the width its layout gives it, which inside a Field is the Field's whole width (FLD-1), so that the box fills its Field — with no width of its own and never the platform's default width for a text element, as the text input is (TXI-63). It SHALL never be narrower than its paddings, its prefix and suffix and room for three digits together need, nor than its paddings and its steppers WHILE they are shown. WHILE the place it is given is narrower than the input's line — the paddings, the prefix, three digits and the suffix — and the steppers need beside each other, THE number input SHALL put the steppers on a row of their own below the input's line, inside the container: at its end, the one that steps down first as on one row, stepper.inset from the end edge and from the bottom edge, the input's line then taking the whole first row and ending at the text padding; and it SHALL put them back beside the line WHEN the two fit again. It SHALL never hide the steppers to fit, because a person without arrow keys, on touch, steps by them alone. A product that wants a narrower box gives it a narrower place.
NMI-37 WHILE the text size is enlarged THE number input SHALL grow with its texts: a floating label wraps and never clips, and the container grows with the input's line, keeping the height of its size as a minimum; the number stays on one line and scrolls inside its input, the prefix and the suffix grow with it and are never cut, and the steppers keep Button's sizes. At 200% no text SHALL be lost, lie over another part or need scrolling in two directions: where the input's line and the steppers no longer fit on one row, the steppers go to the row below it (NMI-36). The rows of the Field that frames it grow as FLD-16 says.
NMI-38 WHERE the platform draws with a person's own forced colors (the web's forced-colors mode, and a platform's equivalent where it has one) THE number input SHALL draw its number, its placeholder, its prefix and its suffix, and its floating label with its asterisk, in the platform's text color, SHALL draw an edge comp.input-box.outline.width wide in the platform's control text color around its container in every variant, and that edge in the platform's highlight color at the focused width while focused, and SHALL rely on no color of its own to mark an error: the error is carried by the invalid state (NMI-10) and by the words its Field shows (FLD-17). The steppers are Buttons and are drawn as Buttons are there; a disabled field and a disabled stepper take the platform's disabled text color.
NMI-39 THE number input SHALL treat its prefix, its number and its suffix as one run of writing — the prefix written before the number and the suffix after it (NMI-28) — and SHALL lay the three out in that order along the run's own direction, in every layout direction: an affix is part of what is written and not a control, so THE number input SHALL NOT place it by the layout direction as it places a stepper. The run's direction SHALL be the direction of the first strong character of prefixText and, WHERE prefixText has none, of suffixText — a letter, or a direction mark the product wrote there — and left-to-right WHERE neither has one (no affix, or signs alone: "$", "%", "×", "°"), because a number is written left to right; it is the direction the bidirectional algorithm gives the one text of prefix, number and suffix as a paragraph of its own. So a unit or a currency written in Latin letters or in signs stands in a right-to-left layout exactly as in a left-to-right one — "44 px", "$ 120 USD", "40 %", "1.5 ×", the number before its unit from left to right — and a unit written in a right-to-left script stands at the number's left, with a prefix of that script at its right, in a left-to-right layout too. THE number input SHALL decide the run's direction from prefixText and suffixText alone — never from the text being typed, from value or from locale — so that nothing changes sides while the person types. WHERE the layout direction is right-to-left THE number input SHALL mirror everything that is not the run: a floating label standing from the right edge with its asterisk to its left, and the steppers, which are controls, at the container's left end with the one that steps down to the right of the one that steps up, so that down is still the first of the two in reading order; all paddings applied by logical start and end, and the input line standing between the container's start padding and the steppers whatever its run's direction. THE number input SHALL NOT mirror the number: its digits, its sign and its separator SHALL be laid out left to right, and the number SHALL be aligned in its input as NMI-29 says.
NMI-40 THE number input SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the number input is shown THE number input SHALL draw itself for the new direction at once, without a motion of its own, keeping its value, its text, its caret and its focus.
NMI-41 WHERE the layout direction is right-to-left THE number input SHALL draw the glyphs of its steppers, remove and add, unmirrored: neither is marked as mirrored in the system's icon set, and the order NMI-39 gives the two steppers is the whole of their mirroring. THE number input SHALL NOT mirror a glyph itself, has no slot and draws no other glyph: the glyph before a label is the Field's, and the Field mirrors it (FLD-20).
NMI-42 THE number input SHALL lay out its floating label, its placeholder, its prefix and its suffix as text of its own direction, isolated from the text around it: each takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right word, a unit and a currency code keep their own order in a right-to-left layout and reorder nothing beside them; THE number input SHALL still place its floating label and its placeholder, and align their lines, by the layout direction, not by the text's own; the prefix and the suffix it places by the direction of the number's run (NMI-39), which the same characters decide. The texts of the rows around it are laid out so by the Field that frames it (FLD-21).
NMI-43 WHEN variant or size names a value that the active design system does not have THE number input SHALL render with the default value's tokens, and in debug builds report the unknown value; WHEN variant names a value the active system has but the number input does not know by name (one a layer added) it SHALL draw no label of its own and draw its edge as a stacked number input does (NMI-15), with that value's own tokens; WHEN align names a value the number input does not know it SHALL behave as start and in debug builds report it. WHEN locale is not a language tag the platform knows THE number input SHALL use the platform's current locale and in debug builds report it.
NMI-44 THE number input SHALL have no words of its own: it SHALL take incrementLabel and decrementLabel from the instance's prop and, WHILE that is empty, from the product's words of the same name — the words a product sets once for all its components — and from nowhere else. IF neither gives incrementLabel or decrementLabel WHILE the steppers are drawn THE number input SHALL still draw both steppers and keep them working for a pointer, SHALL keep the one without a name from assistive technology — the spin button's own increase and decrease actions (NMI-34) do its work there, and a button without a name is worse than none — and in debug builds SHALL report the missing words once. The digits, the separator and the minus sign it shows are the platform's locale data and not the number input's words (NMI-17); invalidText is the instance's alone; and the words of a requirement are the Field's (FLD-24).
NMI-45 THE number input SHALL say to the Field that frames it what only the input knows, and SHALL say it again WHEN it changes: that it draws the label itself WHILE its variant floats one (NMI-8), so that the Field draws no label row (FLD-25); how far the line of its number stands from its own start edge and from its end edge — its inline padding at each (NMI-1), where the input line starts with whichever affix stands there (NMI-39), and, at the end, also WHILE the steppers are drawn, because a line of help is aligned with the text above it and not with a button — so that the Field's supporting row starts and ends there (FLD-26); and an entry error with its words (NMI-21, FLD-28). It keeps no count, so a number field fills its Field's count text with nothing (FLD-27). It SHALL take from that Field its name, its description and its required, invalid and disabled states (NMI-10, NMI-12, NMI-32), whether the label is hidden (NMI-8) and whether an asterisk follows it (NMI-7), so that a Field and a number input placed by hand are drawn, named, described and marked exactly as a number field is.
NMI-46 THE number input SHALL draw no label row above itself, no requirement tag, no help and no error text: those are the rows of the Field that frames it (FLD-2 to FLD-7). WHILE no Field frames it THE number input SHALL be complete as a bare control — its box, the label that floats in it where its variant floats one, the number, its unit and its steppers, its states and its events — and exactly as tall as its container, for a number whose visible label is elsewhere: the head of a table's column, a toolbar, the title of a row with a slider beside the input.
NMI-47 THE number input SHALL draw its box from the one set of tokens every input box is drawn from, the group comp.input-box (comp.input-box.container.shape, comp.input-box.container.height.medium, and so on): its container, its corners, its edges and their widths, the label that floats in it and the asterisk after it, the figure and the placeholder, the prefix and the suffix, the caret and the highlight of selected text, the paddings, the gaps and the least height of its size (a stacked box's from comp.input-box.stacked.container.height.medium and comp.input-box.stacked.container.padding-block.medium for size medium, as tall as a Button of the same size, and a filled or an outlined box's from comp.input-box.container.height.medium and comp.input-box.container.padding-block.medium, and so on), and the colors of each of these in its look, at rest, hovered, focused and disabled, plain and in error. It SHALL have no token of its own for any of them: the tokens of its own group say only where its steppers stand and how a held stepper repeats (NMI-25). WHEN a design system or a product sets a token of the box THE number input SHALL follow it, as the text input, the select, the combobox and the color input do, which read the same tokens; no token is held equal to another for this. WHERE one number input is to differ from the other boxes of a product, the token is set for that instance — on its element or on a subtree that holds it, in each platform's way of setting a token for a subtree — and THE number input SHALL draw itself from the value it finds there.
NMI-48 WHERE the platform has ARIA attributes (the web), WHEN the product sets an aria- attribute on the host, by the attribute or by the ARIA property that reflects it, THE number input SHALL move it to the data-aria- attribute of the same name, so that the host is no second node with its name, description or state, and SHALL expose it on the element that carries its role — an id reference by the elements it names in the host's tree or a tree around it, an error message as part of the description while the number input is invalid — except those the number input owns (aria-disabled), which it SHALL move and not expose. WHEN the product removes the aria- attribute or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE number input SHALL take it off that element. WHEN the product sets an aria- attribute the number input owns THE number input SHALL report it in debug builds.

各平台名称对照

对等并不意味着 API 完全相同。同一份契约,由各平台以自己的惯用方式表达。

规格 Web SwiftUI Jetpack Compose Flutter React Native
组件 tanrei-number-input TanreiNumberInput TanreiNumberInput TanreiNumberInput NumberInput
variant · 属性 variant variant variant variant variant
size · 属性 size size size size size
value · 属性 value value value value value
label · 属性 label label label labelText label
labelHidden · 属性 labelHidden labelsHidden labelHidden labelHidden labelHidden
placeholder · 属性 placeholder prompt placeholder hintText placeholder
error · 属性 error error isError isError error
disabled · 属性 disabled disabled enabled enabled disabled
readOnly · 属性 readOnly readOnly readOnly readOnly readOnly
min · 属性 min in min min min
max · 属性 max in max max max
step · 属性 step step step step step
steppers · 属性 noSteppers steppers steppers steppers steppers
incrementLabel · 属性 incrementLabel incrementLabel incrementContentDescription incrementSemanticLabel incrementAccessibilityLabel
decrementLabel · 属性 decrementLabel decrementLabel decrementContentDescription decrementSemanticLabel decrementAccessibilityLabel
prefixText · 属性 prefixText prefix prefix prefixText prefix
suffixText · 属性 suffixText suffix suffix suffixText suffix
valueText · 属性 valueText valueText stateDescription semanticValue valueText
align · 属性 align align align align align
minFractionDigits · 属性 minFractionDigits minFractionDigits minFractionDigits minFractionDigits minFractionDigits
maxFractionDigits · 属性 maxFractionDigits maxFractionDigits maxFractionDigits maxFractionDigits maxFractionDigits
locale · 属性 locale locale locale locale locale
invalidText · 属性 invalidText invalidText invalidText invalidText invalidText
input · 事件 input onInput onInput onInput onInput
change · 事件 change value onValueChange onChanged onValueChange
action · 事件 action onSubmit keyboardActions onSubmitted onSubmitEditing
entryError · 事件 entryerror onEntryError onEntryError onEntryError onEntryError
备注 The number field's control without the frame: the class TanreiNumberInput, React NumberInput. A form-associated custom element (static formAssociated = true) with a shadow root that does not delegate focus: it takes name, submits the value as a plain decimal string ("12.5", never the locale's "12,5"), restores it on navigation, resets to the value it was first given, follows <fieldset disabled> and reports valueMissing while its Field says it is required and badInput while an entry error lasts (NMI-35). The host is block-level (display: flex): it is as wide as its place, which inside a Field is the Field's whole width (FLD-1), and has no width of its own: the inner input's default width is never the host's (NMI-36). Everything the number field's note says of its inner element holds here, --tanrei-comp-input-box-* for the box (NMI-47) and --tanrei-comp-number-input-* for the steppers' place and the timing of a hold: <input type="text" inputmode="decimal"> (numeric while maxFractionDigits is 0; text on platforms whose decimal keypad has no minus sign while negative numbers are allowed) with role="spinbutton", aria-valuenow, aria-valuemin, aria-valuemax, aria-valuetext, aria-invalid, aria-required and aria-readonly set on that element — not through ElementInternals, which axe cannot see — and autocomplete="off", autocorrect="off", spellcheck="false"; not <input type="number"> (the accessibility notes); Intl.NumberFormat with useGrouping: false, and without locale the nearest lang attribute, then navigator.language; font-variant-numeric: tabular-nums; no wheel listener. The steppers are two <tanrei-button variant="text"> kept out of the focus order, named by label; they listen to pointerdown, not click, so that a hold repeats and focus does not move. Parts: container, outline, active-indicator, label (the floating label), requirement-label (its asterisk), input, prefix, suffix, stepper. The connection with a Field is tanrei-field-connect (src/primitives/field.ts), in both directions (NMI-45): the input says drawsLabel while its look floats a label, inset: { start, end } — its inline padding at both edges, with and without steppers — and refusal: { text } while an entry error lasts. It keeps no count. It takes the name, the description, invalid, required and disabled, and labelHidden and requiredMark (asterisk or none), and writes the name, the description and the states on the inner element, its own label winning. The value property is number | null; the value attribute is the decimal string. Attributes are kebab-case (label-hidden, increment-label, decrement-label, prefix-text, suffix-text, value-text, min-fraction-digits, max-fraction-digits, invalid-text) except readonly, min, max and step, which keep HTML's names; a boolean attribute cannot default to true, so the web names the inverse: no-steppers (the noSteppers property). input is a composed, bubbling CustomEvent whose detail.value is the number the text reads as, or null; the native input's own input event is stopped at the shadow root. change and action are composed, bubbling events (the host's value is set before change fires; action is cancelable) and entryerror a CustomEvent whose detail holds reason and text, the input's invalidText. Before the element is defined it has no size and shows nothing. Under forced-colors: active the container has a ButtonText border and the texts are CanvasText. Words: increment-label and decrement-label left empty read the product's words, set once with setWords() from @tanrei/web; the element draws again when they change. The input line (.body) takes the direction of the number's run (NMI-39) as its own dir — computed from prefixText and suffixText by their first strong character (a letter of a right-to-left script, U+200F or U+061C: right-to-left; any other letter or U+200E, or no strong character: left-to-right) — so the three stand in written order along it and their gaps stay logical; the container, the steppers and the floating label keep the host's direction. The native input keeps dir="ltr" and its text-align is physical, from align, the layout's direction and the run's (NMI-29). Not built on this platform yet: the number field's control as a view of its own, with the names the number field's spec gives these props. What that spec's note says of the text element, the keyboard, the formatter, the adjustable semantics and the steppers is this view's. The Field around it gives the name, the description and the states through the platform's own channel, and the input says what it draws back up (NMI-45). The prefix, the number and the suffix are one run whose direction is that of the affixes' first strong character, and left-to-right where they have none (NMI-39), so the row that holds the three takes that direction and not the layout's; align end is the end of that run (NMI-29); the steppers and the label follow the layout. Not built on this platform yet: the number field's control as a view of its own, with the names the number field's spec gives these props. What that spec's note says of the text element, the keyboard, the formatter, the adjustable semantics and the steppers is this view's. The Field around it gives the name, the description and the states through the platform's own channel, and the input says what it draws back up (NMI-45). The prefix, the number and the suffix are one run whose direction is that of the affixes' first strong character, and left-to-right where they have none (NMI-39), so the row that holds the three takes that direction and not the layout's; align end is the end of that run (NMI-29); the steppers and the label follow the layout. Not built on this platform yet: the number field's control as a view of its own, with the names the number field's spec gives these props. What that spec's note says of the text element, the keyboard, the formatter, the adjustable semantics and the steppers is this view's. The Field around it gives the name, the description and the states through the platform's own channel, and the input says what it draws back up (NMI-45). The prefix, the number and the suffix are one run whose direction is that of the affixes' first strong character, and left-to-right where they have none (NMI-39), so the row that holds the three takes that direction and not the layout's; align end is the end of that run (NMI-29); the steppers and the label follow the layout. Not built on this platform yet: the number field's control as a view of its own, with the names the number field's spec gives these props. What that spec's note says of the text element, the keyboard, the formatter, the adjustable semantics and the steppers is this view's. The Field around it gives the name, the description and the states through the platform's own channel, and the input says what it draws back up (NMI-45). The prefix, the number and the suffix are one run whose direction is that of the affixes' first strong character, and left-to-right where they have none (NMI-39), so the row that holds the three takes that direction and not the layout's; align end is the end of that run (NMI-29); the steppers and the label follow the layout.

令牌

令牌 引用 解析值(浅色)
--tanrei-comp-number-input-motion-repeat-delay sys.motion.duration.long2 500ms
--tanrei-comp-number-input-motion-repeat-interval sys.motion.duration.short2 100ms
--tanrei-comp-number-input-motion-repeat-interval-fast sys.motion.duration.short1 50ms
--tanrei-comp-number-input-stepper-gap sys.space.space0 0px
--tanrei-comp-number-input-stepper-inset sys.space.space50 4px
--tanrei-comp-input-box-active-indicator-width sys.border.hairline 1px
--tanrei-comp-input-box-affix-gap sys.space.space25 2px
--tanrei-comp-input-box-container-gap-large sys.space.space200 16px
--tanrei-comp-input-box-container-gap-medium sys.space.space200 16px
--tanrei-comp-input-box-container-gap-small sys.space.space100 8px
--tanrei-comp-input-box-container-height-large — 56px
--tanrei-comp-input-box-container-height-medium — 48px
--tanrei-comp-input-box-container-height-small — 40px
--tanrei-comp-input-box-container-padding-block-with-label-large — 4px
--tanrei-comp-input-box-container-padding-block-with-label-medium — 4px
--tanrei-comp-input-box-container-padding-block-with-label-small — 2px
--tanrei-comp-input-box-container-padding-block-large — 14px
--tanrei-comp-input-box-container-padding-block-medium — 10px
--tanrei-comp-input-box-container-padding-block-small — 8px
--tanrei-comp-input-box-container-padding-inline-with-icon-large sys.space.space150 12px
--tanrei-comp-input-box-container-padding-inline-with-icon-medium sys.space.space150 12px
--tanrei-comp-input-box-container-padding-inline-with-icon-small sys.space.space100 8px
--tanrei-comp-input-box-container-padding-inline-large sys.space.space200 16px
--tanrei-comp-input-box-container-padding-inline-medium sys.space.space200 16px
--tanrei-comp-input-box-container-padding-inline-small sys.space.space150 12px
--tanrei-comp-input-box-container-shape sys.shape.corner.small 6px
--tanrei-comp-input-box-container-shape-bottom sys.shape.corner.none 0px
--tanrei-comp-input-box-disabled-active-indicator-width — 1px
--tanrei-comp-input-box-disabled-opacity — 1
--tanrei-comp-input-box-disabled-outline-width — 1px
--tanrei-comp-input-box-filled-active-indicator-color sys.color.outline #848484
--tanrei-comp-input-box-filled-affix-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-filled-caret-color sys.color.primary #1b1b1b
--tanrei-comp-input-box-filled-container-color sys.color.surface-container-highest #eeeeee
--tanrei-comp-input-box-filled-disabled-active-indicator-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-affix-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-caret-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-container-color — #eeeeee
--tanrei-comp-input-box-filled-disabled-error-active-indicator-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-error-affix-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-error-caret-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-error-container-color — #eeeeee
--tanrei-comp-input-box-filled-disabled-error-input-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-error-label-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-error-leading-icon-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-error-outline-color — #000000 · 0%
--tanrei-comp-input-box-filled-disabled-error-placeholder-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-error-required-asterisk-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-error-state-layer-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-error-trailing-icon-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-input-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-leading-icon-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-outline-color — #000000 · 0%
--tanrei-comp-input-box-filled-disabled-placeholder-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-required-asterisk-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-disabled-trailing-icon-color — #1b1b1b · 38%
--tanrei-comp-input-box-filled-error-active-indicator-color sys.color.error #cd262f
--tanrei-comp-input-box-filled-error-affix-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-filled-error-caret-color sys.color.error #cd262f
--tanrei-comp-input-box-filled-error-container-color sys.color.surface-container-highest #eeeeee
--tanrei-comp-input-box-filled-error-input-color sys.color.on-surface #1b1b1b
--tanrei-comp-input-box-filled-error-label-color sys.color.error #cd262f
--tanrei-comp-input-box-filled-error-leading-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-filled-error-outline-color — #000000 · 0%
--tanrei-comp-input-box-filled-error-placeholder-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-filled-error-required-asterisk-color sys.color.primary #1b1b1b
--tanrei-comp-input-box-filled-error-state-layer-color comp.input-box.filled.error.input.color #1b1b1b
--tanrei-comp-input-box-filled-error-trailing-icon-color sys.color.error #cd262f
--tanrei-comp-input-box-filled-focused-active-indicator-color — #0070ac
--tanrei-comp-input-box-filled-focused-affix-color — #4b4b4b
--tanrei-comp-input-box-filled-focused-caret-color — #1b1b1b
--tanrei-comp-input-box-filled-focused-container-color — #eeeeee
--tanrei-comp-input-box-filled-focused-error-active-indicator-color — #cd262f
--tanrei-comp-input-box-filled-focused-error-affix-color — #4b4b4b
--tanrei-comp-input-box-filled-focused-error-caret-color — #cd262f
--tanrei-comp-input-box-filled-focused-error-container-color — #eeeeee
--tanrei-comp-input-box-filled-focused-error-input-color — #1b1b1b
--tanrei-comp-input-box-filled-focused-error-label-color — #cd262f
--tanrei-comp-input-box-filled-focused-error-leading-icon-color — #4b4b4b
--tanrei-comp-input-box-filled-focused-error-outline-color — #000000 · 0%
--tanrei-comp-input-box-filled-focused-error-placeholder-color — #4b4b4b
--tanrei-comp-input-box-filled-focused-error-required-asterisk-color — #1b1b1b
--tanrei-comp-input-box-filled-focused-error-state-layer-color — #1b1b1b
--tanrei-comp-input-box-filled-focused-error-trailing-icon-color — #cd262f
--tanrei-comp-input-box-filled-focused-input-color — #1b1b1b
--tanrei-comp-input-box-filled-focused-label-color — #1b1b1b
--tanrei-comp-input-box-filled-focused-leading-icon-color — #4b4b4b
--tanrei-comp-input-box-filled-focused-outline-color — #000000 · 0%
--tanrei-comp-input-box-filled-focused-placeholder-color — #4b4b4b
--tanrei-comp-input-box-filled-focused-required-asterisk-color — #1b1b1b
--tanrei-comp-input-box-filled-focused-state-layer-color — #1b1b1b
--tanrei-comp-input-box-filled-focused-trailing-icon-color — #4b4b4b
--tanrei-comp-input-box-filled-hovered-active-indicator-color — #1b1b1b
--tanrei-comp-input-box-filled-hovered-affix-color — #4b4b4b
--tanrei-comp-input-box-filled-hovered-caret-color — #1b1b1b
--tanrei-comp-input-box-filled-hovered-container-color — #dddddd
--tanrei-comp-input-box-filled-hovered-error-active-indicator-color — #cd262f
--tanrei-comp-input-box-filled-hovered-error-affix-color — #4b4b4b
--tanrei-comp-input-box-filled-hovered-error-caret-color — #cd262f
--tanrei-comp-input-box-filled-hovered-error-container-color — #dddddd
--tanrei-comp-input-box-filled-hovered-error-input-color — #1b1b1b
--tanrei-comp-input-box-filled-hovered-error-label-color — #cd262f
--tanrei-comp-input-box-filled-hovered-error-leading-icon-color — #4b4b4b
--tanrei-comp-input-box-filled-hovered-error-outline-color — #000000 · 0%
--tanrei-comp-input-box-filled-hovered-error-placeholder-color — #4b4b4b
--tanrei-comp-input-box-filled-hovered-error-required-asterisk-color — #1b1b1b
--tanrei-comp-input-box-filled-hovered-error-state-layer-color — #1b1b1b
--tanrei-comp-input-box-filled-hovered-error-trailing-icon-color — #cd262f
--tanrei-comp-input-box-filled-hovered-input-color — #1b1b1b
--tanrei-comp-input-box-filled-hovered-label-color — #4b4b4b
--tanrei-comp-input-box-filled-hovered-leading-icon-color — #4b4b4b
--tanrei-comp-input-box-filled-hovered-outline-color — #000000 · 0%
--tanrei-comp-input-box-filled-hovered-placeholder-color — #4b4b4b
--tanrei-comp-input-box-filled-hovered-required-asterisk-color — #1b1b1b
--tanrei-comp-input-box-filled-hovered-state-layer-color — #1b1b1b
--tanrei-comp-input-box-filled-hovered-trailing-icon-color — #4b4b4b
--tanrei-comp-input-box-filled-input-color sys.color.on-surface #1b1b1b
--tanrei-comp-input-box-filled-label-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-filled-leading-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-filled-outline-color — #000000 · 0%
--tanrei-comp-input-box-filled-placeholder-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-filled-required-asterisk-color sys.color.primary #1b1b1b
--tanrei-comp-input-box-filled-state-layer-color comp.input-box.filled.input.color #1b1b1b
--tanrei-comp-input-box-filled-trailing-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-focused-active-indicator-width — 2px
--tanrei-comp-input-box-focused-opacity — 1
--tanrei-comp-input-box-focused-outline-width — 2px
--tanrei-comp-input-box-hovered-active-indicator-width — 1px
--tanrei-comp-input-box-hovered-opacity — 1
--tanrei-comp-input-box-hovered-outline-width — 1px
--tanrei-comp-input-box-icon-size-large sys.icon.medium 24px
--tanrei-comp-input-box-icon-size-medium sys.icon.medium 24px
--tanrei-comp-input-box-icon-size-small sys.icon.small 20px
--tanrei-comp-input-box-motion-label-duration sys.motion.role.state.default 250ms
--tanrei-comp-input-box-motion-label-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-input-box-motion-state-duration-large sys.motion.role.state.default 250ms
--tanrei-comp-input-box-motion-state-duration-medium sys.motion.role.state.default 250ms
--tanrei-comp-input-box-motion-state-duration-small sys.motion.role.state.compact 200ms
--tanrei-comp-input-box-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-input-box-outline-notch-padding sys.space.space50 4px
--tanrei-comp-input-box-outline-width sys.border.hairline 1px
--tanrei-comp-input-box-outlined-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-outlined-affix-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-outlined-caret-color sys.color.primary #1b1b1b
--tanrei-comp-input-box-outlined-container-color — #000000 · 0%
--tanrei-comp-input-box-outlined-disabled-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-outlined-disabled-affix-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-caret-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-container-color — #000000 · 0%
--tanrei-comp-input-box-outlined-disabled-error-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-outlined-disabled-error-affix-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-error-caret-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-error-container-color — #000000 · 0%
--tanrei-comp-input-box-outlined-disabled-error-input-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-error-label-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-error-leading-icon-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-error-outline-color — #1b1b1b · 12%
--tanrei-comp-input-box-outlined-disabled-error-placeholder-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-error-required-asterisk-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-error-state-layer-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-error-trailing-icon-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-input-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-leading-icon-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-outline-color — #1b1b1b · 12%
--tanrei-comp-input-box-outlined-disabled-placeholder-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-required-asterisk-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-disabled-trailing-icon-color — #1b1b1b · 38%
--tanrei-comp-input-box-outlined-error-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-outlined-error-affix-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-outlined-error-caret-color sys.color.error #cd262f
--tanrei-comp-input-box-outlined-error-container-color — #000000 · 0%
--tanrei-comp-input-box-outlined-error-input-color sys.color.on-surface #1b1b1b
--tanrei-comp-input-box-outlined-error-label-color sys.color.error #cd262f
--tanrei-comp-input-box-outlined-error-leading-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-outlined-error-outline-color sys.color.error #cd262f
--tanrei-comp-input-box-outlined-error-placeholder-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-outlined-error-required-asterisk-color sys.color.primary #1b1b1b
--tanrei-comp-input-box-outlined-error-state-layer-color comp.input-box.outlined.error.input.color #1b1b1b
--tanrei-comp-input-box-outlined-error-trailing-icon-color sys.color.error #cd262f
--tanrei-comp-input-box-outlined-focused-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-outlined-focused-affix-color — #4b4b4b
--tanrei-comp-input-box-outlined-focused-caret-color — #1b1b1b
--tanrei-comp-input-box-outlined-focused-container-color — #000000 · 0%
--tanrei-comp-input-box-outlined-focused-error-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-outlined-focused-error-affix-color — #4b4b4b
--tanrei-comp-input-box-outlined-focused-error-caret-color — #cd262f
--tanrei-comp-input-box-outlined-focused-error-container-color — #000000 · 0%
--tanrei-comp-input-box-outlined-focused-error-input-color — #1b1b1b
--tanrei-comp-input-box-outlined-focused-error-label-color — #cd262f
--tanrei-comp-input-box-outlined-focused-error-leading-icon-color — #4b4b4b
--tanrei-comp-input-box-outlined-focused-error-outline-color — #cd262f
--tanrei-comp-input-box-outlined-focused-error-placeholder-color — #4b4b4b
--tanrei-comp-input-box-outlined-focused-error-required-asterisk-color — #1b1b1b
--tanrei-comp-input-box-outlined-focused-error-state-layer-color — #1b1b1b
--tanrei-comp-input-box-outlined-focused-error-trailing-icon-color — #cd262f
--tanrei-comp-input-box-outlined-focused-input-color — #1b1b1b
--tanrei-comp-input-box-outlined-focused-label-color — #1b1b1b
--tanrei-comp-input-box-outlined-focused-leading-icon-color — #4b4b4b
--tanrei-comp-input-box-outlined-focused-outline-color — #0070ac
--tanrei-comp-input-box-outlined-focused-placeholder-color — #4b4b4b
--tanrei-comp-input-box-outlined-focused-required-asterisk-color — #1b1b1b
--tanrei-comp-input-box-outlined-focused-state-layer-color — #1b1b1b
--tanrei-comp-input-box-outlined-focused-trailing-icon-color — #4b4b4b
--tanrei-comp-input-box-outlined-hovered-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-outlined-hovered-affix-color — #4b4b4b
--tanrei-comp-input-box-outlined-hovered-caret-color — #1b1b1b
--tanrei-comp-input-box-outlined-hovered-container-color — #000000 · 0%
--tanrei-comp-input-box-outlined-hovered-error-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-outlined-hovered-error-affix-color — #4b4b4b
--tanrei-comp-input-box-outlined-hovered-error-caret-color — #cd262f
--tanrei-comp-input-box-outlined-hovered-error-container-color — #000000 · 0%
--tanrei-comp-input-box-outlined-hovered-error-input-color — #1b1b1b
--tanrei-comp-input-box-outlined-hovered-error-label-color — #cd262f
--tanrei-comp-input-box-outlined-hovered-error-leading-icon-color — #4b4b4b
--tanrei-comp-input-box-outlined-hovered-error-outline-color — #cd262f
--tanrei-comp-input-box-outlined-hovered-error-placeholder-color — #4b4b4b
--tanrei-comp-input-box-outlined-hovered-error-required-asterisk-color — #1b1b1b
--tanrei-comp-input-box-outlined-hovered-error-state-layer-color — #1b1b1b
--tanrei-comp-input-box-outlined-hovered-error-trailing-icon-color — #cd262f
--tanrei-comp-input-box-outlined-hovered-input-color — #1b1b1b
--tanrei-comp-input-box-outlined-hovered-label-color — #4b4b4b
--tanrei-comp-input-box-outlined-hovered-leading-icon-color — #4b4b4b
--tanrei-comp-input-box-outlined-hovered-outline-color — #1b1b1b
--tanrei-comp-input-box-outlined-hovered-placeholder-color — #4b4b4b
--tanrei-comp-input-box-outlined-hovered-required-asterisk-color — #1b1b1b
--tanrei-comp-input-box-outlined-hovered-state-layer-color — #1b1b1b
--tanrei-comp-input-box-outlined-hovered-trailing-icon-color — #4b4b4b
--tanrei-comp-input-box-outlined-input-color sys.color.on-surface #1b1b1b
--tanrei-comp-input-box-outlined-label-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-outlined-leading-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-outlined-outline-color sys.color.outline #848484
--tanrei-comp-input-box-outlined-placeholder-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-outlined-required-asterisk-color sys.color.primary #1b1b1b
--tanrei-comp-input-box-outlined-state-layer-color comp.input-box.outlined.input.color #1b1b1b
--tanrei-comp-input-box-outlined-trailing-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-requirement-asterisk-gap sys.space.space25 2px
--tanrei-comp-input-box-selection-color sys.color.primary #1b1b1b
--tanrei-comp-input-box-selection-content-color sys.color.on-primary #ffffff
--tanrei-comp-input-box-stacked-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-stacked-affix-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-stacked-caret-color sys.color.primary #1b1b1b
--tanrei-comp-input-box-stacked-container-color sys.color.surface-container-highest #eeeeee
--tanrei-comp-input-box-stacked-container-height-large comp.button.container.height.large 56px
--tanrei-comp-input-box-stacked-container-height-medium comp.button.container.height.medium 40px
--tanrei-comp-input-box-stacked-container-height-small comp.button.container.height.small 32px
--tanrei-comp-input-box-stacked-container-padding-block-large — 14px
--tanrei-comp-input-box-stacked-container-padding-block-medium — 6px
--tanrei-comp-input-box-stacked-container-padding-block-small — 4px
--tanrei-comp-input-box-stacked-disabled-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-stacked-disabled-affix-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-caret-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-container-color — #eeeeee
--tanrei-comp-input-box-stacked-disabled-error-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-stacked-disabled-error-affix-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-error-caret-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-error-container-color — #eeeeee
--tanrei-comp-input-box-stacked-disabled-error-input-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-error-label-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-error-leading-icon-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-error-outline-color — #000000 · 0%
--tanrei-comp-input-box-stacked-disabled-error-placeholder-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-error-required-asterisk-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-error-state-layer-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-error-trailing-icon-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-input-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-leading-icon-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-outline-color — #000000 · 0%
--tanrei-comp-input-box-stacked-disabled-placeholder-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-required-asterisk-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-disabled-trailing-icon-color — #1b1b1b · 38%
--tanrei-comp-input-box-stacked-error-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-stacked-error-affix-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-stacked-error-caret-color sys.color.error #cd262f
--tanrei-comp-input-box-stacked-error-container-color sys.color.surface-container-highest #eeeeee
--tanrei-comp-input-box-stacked-error-input-color sys.color.on-surface #1b1b1b
--tanrei-comp-input-box-stacked-error-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-input-box-stacked-error-leading-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-stacked-error-outline-color sys.color.error #cd262f
--tanrei-comp-input-box-stacked-error-placeholder-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-stacked-error-required-asterisk-color sys.color.primary #1b1b1b
--tanrei-comp-input-box-stacked-error-state-layer-color comp.input-box.stacked.error.input.color #1b1b1b
--tanrei-comp-input-box-stacked-error-trailing-icon-color sys.color.error #cd262f
--tanrei-comp-input-box-stacked-focused-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-stacked-focused-affix-color — #4b4b4b
--tanrei-comp-input-box-stacked-focused-caret-color — #1b1b1b
--tanrei-comp-input-box-stacked-focused-container-color — #eeeeee
--tanrei-comp-input-box-stacked-focused-error-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-stacked-focused-error-affix-color — #4b4b4b
--tanrei-comp-input-box-stacked-focused-error-caret-color — #cd262f
--tanrei-comp-input-box-stacked-focused-error-container-color — #eeeeee
--tanrei-comp-input-box-stacked-focused-error-input-color — #1b1b1b
--tanrei-comp-input-box-stacked-focused-error-label-color — #1b1b1b
--tanrei-comp-input-box-stacked-focused-error-leading-icon-color — #4b4b4b
--tanrei-comp-input-box-stacked-focused-error-outline-color — #cd262f
--tanrei-comp-input-box-stacked-focused-error-placeholder-color — #4b4b4b
--tanrei-comp-input-box-stacked-focused-error-required-asterisk-color — #1b1b1b
--tanrei-comp-input-box-stacked-focused-error-state-layer-color — #1b1b1b
--tanrei-comp-input-box-stacked-focused-error-trailing-icon-color — #cd262f
--tanrei-comp-input-box-stacked-focused-input-color — #1b1b1b
--tanrei-comp-input-box-stacked-focused-label-color — #1b1b1b
--tanrei-comp-input-box-stacked-focused-leading-icon-color — #4b4b4b
--tanrei-comp-input-box-stacked-focused-outline-color — #0070ac
--tanrei-comp-input-box-stacked-focused-placeholder-color — #4b4b4b
--tanrei-comp-input-box-stacked-focused-required-asterisk-color — #1b1b1b
--tanrei-comp-input-box-stacked-focused-state-layer-color — #1b1b1b
--tanrei-comp-input-box-stacked-focused-trailing-icon-color — #4b4b4b
--tanrei-comp-input-box-stacked-hovered-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-stacked-hovered-affix-color — #4b4b4b
--tanrei-comp-input-box-stacked-hovered-caret-color — #1b1b1b
--tanrei-comp-input-box-stacked-hovered-container-color — #dddddd
--tanrei-comp-input-box-stacked-hovered-error-active-indicator-color — #000000 · 0%
--tanrei-comp-input-box-stacked-hovered-error-affix-color — #4b4b4b
--tanrei-comp-input-box-stacked-hovered-error-caret-color — #cd262f
--tanrei-comp-input-box-stacked-hovered-error-container-color — #dddddd
--tanrei-comp-input-box-stacked-hovered-error-input-color — #1b1b1b
--tanrei-comp-input-box-stacked-hovered-error-label-color — #1b1b1b
--tanrei-comp-input-box-stacked-hovered-error-leading-icon-color — #4b4b4b
--tanrei-comp-input-box-stacked-hovered-error-outline-color — #cd262f
--tanrei-comp-input-box-stacked-hovered-error-placeholder-color — #4b4b4b
--tanrei-comp-input-box-stacked-hovered-error-required-asterisk-color — #1b1b1b
--tanrei-comp-input-box-stacked-hovered-error-state-layer-color — #1b1b1b
--tanrei-comp-input-box-stacked-hovered-error-trailing-icon-color — #cd262f
--tanrei-comp-input-box-stacked-hovered-input-color — #1b1b1b
--tanrei-comp-input-box-stacked-hovered-label-color — #1b1b1b
--tanrei-comp-input-box-stacked-hovered-leading-icon-color — #4b4b4b
--tanrei-comp-input-box-stacked-hovered-outline-color — #000000 · 0%
--tanrei-comp-input-box-stacked-hovered-placeholder-color — #4b4b4b
--tanrei-comp-input-box-stacked-hovered-required-asterisk-color — #1b1b1b
--tanrei-comp-input-box-stacked-hovered-state-layer-color — #1b1b1b
--tanrei-comp-input-box-stacked-hovered-trailing-icon-color — #4b4b4b
--tanrei-comp-input-box-stacked-input-color sys.color.on-surface #1b1b1b
--tanrei-comp-input-box-stacked-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-input-box-stacked-leading-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-stacked-outline-color — #000000 · 0%
--tanrei-comp-input-box-stacked-placeholder-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-stacked-required-asterisk-color sys.color.primary #1b1b1b
--tanrei-comp-input-box-stacked-state-layer-color comp.input-box.stacked.input.color #1b1b1b
--tanrei-comp-input-box-stacked-trailing-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-input-box-input-typescale-large sys.typescale.body-large 400 16px/1.625
--tanrei-comp-input-box-input-typescale-medium sys.typescale.body-large 400 16px/1.625
--tanrei-comp-input-box-input-typescale-small sys.typescale.body 400 14px/1.5714
--tanrei-comp-input-box-label-floated-typescale-large sys.typescale.label-small 500 12px/1.3333
--tanrei-comp-input-box-label-floated-typescale-medium sys.typescale.label-small 500 12px/1.3333
--tanrei-comp-input-box-label-floated-typescale-small sys.typescale.label-small 500 12px/1.3333
--tanrei-comp-input-box-label-typescale-large sys.typescale.label 500 14px/1.4286
--tanrei-comp-input-box-label-typescale-medium sys.typescale.label 500 14px/1.4286
--tanrei-comp-input-box-label-typescale-small sys.typescale.label 500 14px/1.4286

测试样例

作为基准的渲染结果。所有平台都以相同的 ID 为每个样例截取快照,以便相互比较。

生成自 specs/number-input.spec.json, tokens/comp/number-input.tokens.json, tokens/comp/input-box.tokens.json, design/tokens/

Web 上绘制方式的不同之处

  • 这个元素是与表单关联的元素。有 name 时,无论区域设置下显示成什么样,它都把值作为普通的十进制数字(12.5)提交;它重置为 value 属性(attribute)的值,在导航返回时恢复,跟随被禁用的 <fieldset>;所在的字段说明它必填而它为空时报告缺少值,文本不是数字时报告输入有误。
  • 数字在 shadow root 里的原生 <input type="text"> 中编辑,这个元素带有微调按钮的角色和各项值。它不是浏览器自带的数字输入控件。
  • 这个元素是一个块。focus() 会把焦点交给原生元素;在方框上、步进按钮之外的任何地方按下,也会让它获得焦点。
  • steppers 对应的是含义相反的属性(attribute)no-steppers,readOnly 对应的是 readonly。
  • input 是带 detail.value 的 CustomEvent。change 和 action 是普通事件:请从元素上读取 value。除非 action 被取消,否则随后会请求表单提交。entryerror 是 CustomEvent,它的 detail 是 { reason, text }:text 是输入框的 invalidText,没有给出时为空。类型是 TanreiNumberInputInputDetail 和 TanreiNumberInputEntryErrorDetail。
  • 部件:container、outline、active-indicator、label、requirement-label、input、prefix、suffix、stepper。它没有插槽。
  • 没有 locale 时,输入框读取最近的 lang 属性(attribute),其次是浏览器的语言。
  • 除了区域设置自己的分隔符之外,只要这个区域设置不用句点分组,句点也读作小数分隔符。
  • 翻页键与滑块一样,在步长自己的网格上走十步:步长为 2 时,在 42 上按 Page Up 得到 62,再按一次得到 82。
  • 在 iOS 和 iPadOS 上,可以键入负数时,输入框会请求完整键盘,因为那里的小数键盘没有负号。
  • increment-label 和 decrement-label 留空时,从产品的文字里读取(setWords())。
  • 在元素完成定义之前,它没有尺寸。请预留方框的高度。

指南

数字输入框、数字字段,还是手写的字段

你需要 使用
标签、方框、帮助文本和错误 数字字段
只要方框,标签位于别处:滑块旁边、表格的单元格、工具栏 数字输入框,由它自己的 label 命名
在框架里面、方框旁边或下方放你自己的东西 一个字段,里面放一个数字输入框和你自己的元素
不表示数量的数字:邮政编码、卡号 文本字段
在某个范围内凭感觉选择的值 滑块

推荐

  • 给单独使用的输入框一个 label,措辞与可见的标签相同,或者把可见的标签和它所在的位置合起来表达的意思说出来:“数量,橡木书桌”。
  • 让单独使用的输入框靠所在的位置得到宽度:网格的一列、一个单元格、工具栏里有宽度的一项。
  • 把单位写进 suffixText;后缀读出来不通顺的地方,给出 valueText。
  • 给出 incrementLabel 和 decrementLabel:写在输入框上,或者用 setWords() 为整个产品只给一次。
  • 只要你需要的是方框上方的标签、帮助文本和错误,就使用数字字段。

避免

  • 不要为了省下标签的位置,在表单里使用单独的输入框。哪里都看不到标签的输入框,对每个看到它的人来说都只能靠猜。
  • 不要把占位符当作标签。一有人键入它就消失,而且它绝不是名称。
  • 不要在字段里的输入框上设置 label、error 或 disabled。把它们写在字段上,标签和帮助文本才会随之变化。
  • 不要把数字字段放进字段。它本身已经是字段;字段框住的控件是数字输入框。
  • 不要在 input 时保存。每按一次键它都会触发;带有已确认的值的是 change。

无障碍

角色
spinbutton
标注
The accessible name is the label of the Field that frames the input, or its own label where no Field does; a filled or outlined input draws that name floating, and labelHidden only stops the drawing. The placeholder is never the name. The description is what the Field gives: its supporting text with, in error, the error text after it and its optional text; an unframed input in an entry error is described by invalidText. The input is exposed as invalid while error is true, its Field is in error or an entry error lasts; as required while its Field says so, whichever mark is drawn; and as read-only while readOnly is true. It is a spin button whose value is the committed number, whose limits are min and max, and whose value text is valueText or the number with its prefix and suffix. The steppers are buttons named by decrementLabel and incrementLabel; they are not in the focus order.
最小目标
即使外观尺寸更小,点击区域也满足最小目标尺寸。 minimum 32px · pointer 44px · spacing 8px · touch 48px

键盘操作

按键 动作
Tab / Shift+Tab Moves focus to the input, which is the field's one stop, and on from it. The steppers are not stops.
Typing, Backspace, Delete, Left Arrow, Right Arrow, Home, End, selection and clipboard shortcuts Edit the text as the platform's own text element does. Nothing typed is refused, clamped or rounded until the field is committed.
Up Arrow / Down Arrow Step the number up or down by step, to the next number on the step's grid, within min and max (NMI-22, NMI-23).
Page Up / Page Down Step by ten times step.
Enter Commits the text — clamps, rounds, or reports a text that is not a number — emits action, and submits the enclosing form on the web.

备注

  • A number input is for a number the product reads as a number: a quantity, a size, a percentage, an amount. Digits that are not a quantity — a postal code, a card number, a telephone number, a one-time code — are text: use a text input with the matching type, which never steps, clamps or reformats them.
  • Text entry happens in the platform's native text element: on the web an <input type="text"> with an inputmode in the shadow root, carrying the spinbutton role and its value attributes — not the browser's number input, whose spinner cannot be drawn from tokens, whose value is emptied by a text it cannot read, whose parsing ignores the product's locale, and which in some browsers changes its value when the wheel turns over it.
  • The steppers are out of the focus order on purpose (NMI-25): the arrow keys step from the input, as in every spin button, and a form of twenty numbers stays twenty stops long. They stay in the accessibility tree, named, because a person exploring a touch screen with a screen reader has no arrow keys.
  • Two steppers side by side take 88 of the field's width under the default system, each a full target. That is the honest size: a pair stacked inside a 48 field would be two 24-high targets that cannot grow over each other. Where a row cannot afford 88, set steppers to false; the keys, the assistive-technology actions and a Slider beside the field still step.
  • The wheel never changes the number (NMI-24). A number that changes because the page was scrolled past it is a wrong number nobody typed.
  • Limits are applied when the field commits, never while a person types (NMI-20): a text on its way to a valid number must be allowed to pass through invalid ones.
  • Errors are words (WCAG 3.3.1, 1.4.1): a text that is not a number is kept, marked, and described by invalidText; nothing is silently thrown away or silently replaced by the last good number.
  • The unit is read with the value (NMI-34). Where a suffix would be read badly ("×", "px"), give valueText in words.
  • Web: a touch browser zooms the page into an input whose text is smaller than 16 px. Both sizes use 16 px text (comp.input-box.input.typescale.medium); a layer that sets a smaller style brings the zoom back.
  • Heights are minimums: at 200% text size the input grows, a floating label wraps rather than clips, and the number keeps its line.
  • The input has no label row and no message of its own: a Field frames it and draws them (FLD-2 to FLD-11, FLD-25, FLD-26, FLD-28), or the product names it with label where its visible label is elsewhere. Unframed and unnamed, it is reported (NMI-12). Beside a slider under one row title, the slider and the number input are each named in words, given the same limits and step, and each one's input is written to the other's value.
  • How a state looks is a value decision of the active design system; components read the state's tokens and never compute the effect themselves.
  • A number and its affixes are spoken in the order they are written — prefix, number, suffix (NMI-34) — whatever side each is drawn on: the run's direction (NMI-39) changes where the unit stands, never what is said or what a typed or pasted text is read as (NMI-18).

生成自 specs/number-input.spec.json, tokens/sys/measure.tokens.json

组件负责的部分

  • 它是一个微调按钮。它的名称是它自己的 label;它没有 label 时,是包着它的字段的标签。占位符绝不是名称。
  • 它的值是已确认的数字,它的范围是 min 和 max,它的值文本是 valueText,或者带上前缀和后缀的数字。
  • 它的描述是字段给它的内容:帮助文本或错误文本。单独使用并处于输入错误时,描述是 invalidText。
  • 处于错误状态或文本不是数字时,它暴露为无效;所在的字段说明它必填时,暴露为必填;只读和禁用时,也分别暴露出来。
  • 步进按钮是以 decrementLabel 和 incrementLabel 命名的按钮;没有人为它命名的那一个对辅助技术隐藏。它们不在焦点顺序里,因为在输入框上用方向键就能步进;靠触摸探索的人仍然找得到它们。
  • 按下步进按钮不会移动焦点,所以不会唤出屏幕键盘。
  • 输入法正在组合输入时,输入框不解读任何内容,也不处理任何按键。滚轮绝不会改变数字。
  • 焦点由方框自己的边缘来显示,无论来自键盘还是指针。点击区域满足最小目标,每个步进按钮都是完整的目标。
  • 浮动标签后面的星号对辅助技术隐藏:必填状态已经说明了这一点。
  • 文本放大到 200% 时,浮动标签会换行,数字保持在一行之内并滚动。在强制颜色模式下,方框会多出一道边线。在从右到左的布局中,步进按钮和标签会镜像:步进按钮位于左端。数字和它的单位按单位自身所用文字的写法书写:用拉丁字母或符号写的单位位于数字之后,从左到右书写;用页面的文字写的单位位于数字的左侧。朗读的内容不变。
  • 当一行放不下并排的数字和步进按钮时(例如在窄窗口中把文本放大到 200%,并带有前缀和较长后缀),步进按钮会移到数字下方单独的一行,位于方框的末端。它们绝不会被隐藏,因为在触摸屏上它们是步进的唯一方式。
  • 缺少名称会在调试构建中报告。

Web 上的已知差距:

  • Chromium 读出数字时不带单位:它从输入框里的文本取得值文本,而略过输入框指定的那一个。单位要紧的地方,请把它写进标签。

需要你做的事

  • 用 label 为每个单独使用的输入框命名。它旁边可见的文本并不能为它命名。
  • 单独使用的输入框可能出错的地方,要用辅助技术读得到的文字说明哪里不对:一条与页面关联的、你自己的消息,或者包着输入框的字段。
  • 即使没有字段显示 invalidText,也要把它写上:它会随输入框一起朗读。
  • 给滑块和它旁边的数字输入框相同的名称。