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

文本字段

让用户输入和编辑文本——单行,或者随文本增高、可手动调整大小的多行——也可以按区域设置的格式逐个单位地输入日期或时间,或者输入代码并按写下的样子原样保存;它带有可以说明必填或选填的标签,以及可选的图标、前缀和后缀、辅助文本、字符计数和选择器按钮。

content_copy以 Markdown 格式查看
类别
文本输入
令牌
comp.field
层级
组合
构成自
Field, Text input

概览

Web
tanrei-text-field
schedule
SwiftUI
TanreiTextField
schedule
Jetpack Compose
TanreiTextField
schedule
Flutter
TanreiTextField
schedule
React Native
TextField
schedule
不提供的变体
tanrei: filled, outlined

生成自 docs/status/status.json, specs/text-field.spec.json

规格已修订,但尚未在所有平台上实现

本页描述的是规格当前所定的文本字段:stacked 变体,它是默认变体,标签位于文本字段上方;filled 和 outlined 只为 Material 和 cupertino 预设保留;高度为 32、40 和 56(标签浮在里面的 filled 或 outlined 文本字段为 40、48 和 56);带选择器按钮的日期和时间类型;随文本增高、或用调整手柄调整大小的多行文本字段(resize);按分段逐个单位输入的日期或时间;以及把文本字段标记为必填或选填的 required 和 optional。Web 组件已经全部做到,演示也是如此。文本字段还可以保存代码(type="code",以及决定代码行是否换行的 wrap):Web 已经实现,其他平台都还没有。文本字段是包住一个文本输入框的字段,也就是本页描述的样子:Web 也已经实现,并且在 Web 上 required 属性(attribute)已经不存在了。SwiftUI、Compose、Flutter 和 React Native 在上方标记为“待跟进”:在跟上之前,它们提供的是上一版——默认为 outlined,高度为 36 和 40,required 标志不绘制任何标记,没有 stacked 变体,没有日期或时间类型,没有选择器按钮,也没有调整大小的模式——它们的示例展示的也是那一版。

默认的 Stacked:一个必填的文本字段,以及带选择器按钮的日期

表单里凡是要填文本的地方,都使用文本字段:它是最普通的情形,拿来就能用,带有标签、方框、一行帮助文本、值有误时的文字,以及计数。

组成部分是一个字段和一个文本输入框。字段是框架:标签、必填或选填标记、帮助文本、错误和计数。文本输入框是控件:方框,以及在方框里键入的一切。文本字段自己什么都不绘制,也没有令牌:它的每个属性都是这两者之一的属性,原样交给它们。

它是一个块,放在哪里就和那里一样宽,它的方框会把它填满。它没有自己的宽度:想要窄一些的字段,就把它放进窄一些的地方。

交互演示

textemailpasswordnumbersearchtelurlcodedatetimedate-time
每个控件设置实时组件的一个属性,代码也随之更新。设置会保留在页面地址中,打开链接即可看到相同的状态。 代码适用于 Web。其他平台请参阅“各平台名称对照”。

用法

<form>
  <tanrei-text-field name="email" label="邮箱" type="email" autocomplete="email" required required-text="必填"
    supporting-text="仅用于登录。"></tanrei-text-field>
  <tanrei-text-field name="company" label="公司" optional optional-text="选填"></tanrei-text-field>
  <!-- 随文本从 3 行增高到 8 行,之后滚动。 -->
  <tanrei-text-field name="message" label="消息" multiline min-rows="3" max-rows="8"></tanrei-text-field>
  <!-- 用调整手柄手动调整大小。 -->
  <tanrei-text-field name="notes" label="备注" multiline resize="manual" resize-label="调整备注的大小"></tanrei-text-field>
</form>

<!-- 只要方框,放在工具栏里:一个文本输入框,由它自己的 label 命名。导入文本字段时,它也随之完成定义。 -->
<tanrei-text-input label="搜索" type="search" placeholder="搜索组件">
  <span slot="leading-icon">search</span>
</tanrei-text-input>

<!-- 代码:无论页面是什么方向,都从左到右、用代码字体显示,绝不自动更正,也不做拼写检查。 -->
<tanrei-text-field label="匹配模式" type="code" value="tanrei.comp.button.*.container.color"></tanrei-text-field>
<tanrei-text-field label="Webhook 请求体" type="code" multiline min-rows="4" supporting-text="JSON"></tanrei-text-field>
<!-- 不换行:代码的每一行都保持在一行里,长的行在文本字段内部滚动。 -->
<tanrei-text-field label="数据行" type="code" multiline no-wrap></tanrei-text-field>

<!-- 按区域设置的顺序逐个单位输入的日期,以 2026-09-27 的形式保存,带选择器按钮。 -->
<tanrei-text-field id="start" label="开始日期" type="date" min="2026-01-01" disabled-dates="2026-05-01 2026-12-25"
  invalid-text="请把日期填写完整。" unavailable-text="请选择 2026 年及以后的工作日。"
  picker picker-label="选择开始日期" segment-empty-text="未填写"></tanrei-text-field>
<tanrei-date-time-picker id="start-picker" presentation="docked" anchor="start" label="开始日期"></tanrei-date-time-picker>

<script type="module">
  import '@tanrei/web/components/text-field';
  const email = document.querySelector('[name="email"]');
  email.addEventListener('input', () => draft(email.value)); // 每次编辑
  email.addEventListener('change', () => save(email.value)); // 值已改变,并且焦点离开或按下 Enter
  // 校验由你负责:文本字段绝不会自己设置 error。
  email.error = submitted && !isValid(email.value);
  email.errorText = '请输入完整的邮箱地址。';

  // 文本字段只发出请求;由产品呈现选择器,并把值写回来。
  const start = document.querySelector('#start');
  const picker = document.querySelector('#start-picker');
  start.addEventListener('pickerrequest', (e) => {
    Object.assign(picker, { value: e.detail.value, min: e.detail.min, max: e.detail.max, disabledDates: start.disabledDates, open: true });
  });
  picker.addEventListener('change', () => (start.value = picker.value));
  picker.addEventListener('close', () => (picker.open = false));
</script>
import { TextField } from '@tanrei/react';

<TextField
  label="邮箱"
  type="email"
  autocomplete="email"
  value={email}
  onInput={(e) => setEmail(e.currentTarget.value)}
  onChange={() => save(email)}
  required
  requiredText="必填"
  error={submitted && !isValid(email)}
  errorText="请输入完整的邮箱地址。"
/>

<TextField label="消息" multiline minRows={3} maxRows={8} value={message}
  onInput={(e) => setMessage(e.currentTarget.value)} />

// 日期:值为 ISO 8601 格式;只要有分段为空,值就为空。
<TextField
  label="开始日期"
  type="date"
  min="2026-01-01"
  value={start}
  invalidText="请把日期填写完整。"
  unavailableText="请选择 2026 年及以后的日期。"
  segmentEmptyText="未填写"
  picker
  pickerLabel="选择开始日期"
  onChange={(e) => setStart(e.currentTarget.value)}
  onEntryError={(e) => report(e.detail.reason)}
  onPickerRequest={() => setPickerOpen(true)}
/>
import Tanrei

TanreiTextField("邮箱", text: $email, type: .email,
                supportingText: "仅用于登录。",
                errorText: "请输入完整的邮箱地址。",
                error: submitted && !isValid(email),
                textContentType: "email", // HTML 的自动填充名称
                onCommit: save,
                // 把操作传到这里:外层的 .onSubmit 会先于 onCommit 运行。
                onSubmit: signIn)

// 多行用纵向的 axis 加上行数限制的范围来表示。
TanreiTextField("消息", text: $message, axis: .vertical, lineLimit: 3...8)
import androidx.compose.foundation.text.KeyboardActions
import io.github.chnotchy.tanrei.components.TanreiTextField
import io.github.chnotchy.tanrei.components.TanreiTextFieldType

var email by remember { mutableStateOf("") }
TanreiTextField(
    value = email,
    onValueChange = { email = it },
    label = "邮箱",
    type = TanreiTextFieldType.Email,
    contentType = "email", // HTML 的自动填充名称
    supportingText = "仅用于登录。",
    isError = submitted && !isValid(email),
    errorText = "请输入完整的邮箱地址。",
    onCommit = { save(it) },
    keyboardActions = KeyboardActions(onDone = { signIn() }),
)

// singleLine 与规格里的 multiline 相反。
TanreiTextField(value = message, onValueChange = { message = it }, label = "消息", singleLine = false, minLines = 3)
import 'package:tanrei/tanrei.dart';

TanreiTextField(
  controller: email, // 或者 initialValue: '…'
  labelText: '邮箱',
  type: TanreiTextFieldType.email,
  autofillHints: TanreiTextField.autofillHintsFromHtml('email'),
  helperText: '仅用于登录。',
  isError: submitted && !isValid(email.text),
  errorText: '请输入完整的邮箱地址。',
  onCommit: save,
  onSubmitted: (String value) => signIn(),
);

TanreiTextField(labelText: '消息', multiline: true, minLines: 3);
import { TextField } from '@tanrei/react-native';

<TextField
  label="邮箱"
  type="email"
  autoComplete="email"
  value={email}
  onChangeText={setEmail}
  supportingText="仅用于登录。"
  error={submitted && !isValid(email)}
  errorText="请输入完整的邮箱地址。"
  onEndEditing={save}
  onSubmitEditing={signIn}
/>
<TextField label="消息" multiline minRows={3} value={message} onChangeText={setMessage} />

下一层

用它的两个成员手写出来的同一个字段,画面是一样的,对辅助技术来说也是同一个文本框,名称、描述和状态都相同:

<tanrei-field label="邮箱" required required-text="必填"
  supporting-text="仅用于登录。">
  <tanrei-text-input name="email" type="email" autocomplete="email"></tanrei-text-input>
</tanrei-field>

手写出来的同一个字段:里面放着一个文本输入框的字段

需要文本字段没有提供的排布方式时,就往下走一层:

你需要 使用
标签、方框、帮助文本、错误和计数 文本字段
在框架里面、方框的旁边或下方放你自己的东西 一个字段,里面放一个文本输入框和你自己的元素
只要方框,放在位于别处的标签之下:工具栏里的筛选框、表格的单元格、标签在旁边的一行 单独的文本输入框,由它自己的 label 命名

到了下一层,变的是各样东西写在哪里。name 和 value 写在文本输入框上,这时它就是表单遇到的那个控件,它的事件也在它上面监听。label、error、disabled 和必填与否仍然写在字段上,由字段传给控件:不要设置输入框自己的那几个。

有一件事是文本字段做、而手写的字段和文本输入框不做的:让计数保持同步。手写时,设置了 counter 的话,每次变化都要把输入框的计数交给字段:

field.countText = input.countText;
input.addEventListener('input', () => (field.countText = input.countText));

规格

错误、禁用、只读、前后缀和计数器

类型:密码、搜索、数字、电话和 URL

search

必填和选填:小标签,以及星号

多行:随文本增高、用调整手柄调整大小(向下,以及双向)、固定

代码:单行的匹配模式、多行的 JSON,以及不换行的代码行

从右到左的页面里的代码:标签和帮助文本跟随页面,代码从左侧开始

分段输入的日期、时间,以及日期和时间。用 Tab、方向键和数字填写

Filled 和 Outlined:Material 预设的变体

以 m3-like 显示

各个成员提供什么

方面 来自字段 来自文本输入框
属性 label、labelHidden、supportingText、errorText、error、disabled、required、optional、requiredMark、requiredText、optionalText 其余的每一个:variant、size、type、value、placeholder、readOnly、行数和调整大小、wrap、maxLength、counter、前缀和后缀、autocomplete,以及日期或时间的一切
事件 — input、change、action、pickerRequest、entryError
插槽 label-icon leading-icon、trailing-icon
令牌 comp.field.* 方框用 comp.input-box.*,每一种输入方框都读取它;只有文本输入框才绘制的部分用 comp.text-input.*

文本输入框有自己的 label、labelHidden、error 和 disabled。文本字段设置的不是它们:文本字段的 label 是字段的 label,它为输入框命名,在 filled 或 outlined 的字段里,浮在方框中的也是它。

令牌

文本字段没有自己的令牌。它的方框读取文本输入框的令牌,它的各行——标签、标记、帮助文本、错误和计数——读取字段的令牌,无论这些令牌设置在哪里。下面的表格列出的是字段的令牌。要改动某一个实例,就在它上面设置成员的令牌:

<tanrei-text-field label="邮箱"
  style="--tanrei-comp-field-label-gap: 12px"></tanrei-text-field>

构成

构成

绘制在 Web 包所绘的测试样例 outlined-empty 之上。
部件 说明
1 field The Field: the label row with its glyph and its requirement mark, and the supporting row with the help, the error text and the count the text field gives it (TXF-67). In a look whose input floats the label it draws no label row.
2 text-input The text input, as the Field's one control: the box and everything typed in it, the icons, the prefix and the suffix, the segments of a date, the picker button, the resize handle, and the label that floats in the box in the looks that float one.

属性

属性 类型 默认值 说明
variant stacked | filled | outlined "stacked" 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, marked by a tinted box and a bottom line, or by a border. Use one style per form. A design system may leave some out (the Tanrei system ships stacked only) or add values. — Text input
size small | medium | large "medium" Minimum height of a single-line input's container: stacked 32 / 40 / 56, as tall as a Button of the same size, so that an input 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. Medium and large are for forms; small is for an input in an app bar, a toolbar or a dense row of controls, beside a small select or button. The hit area always meets the minimum target. — Text input
type text | email | password | number | search | tel | url | code | date | time | date-time "text" What the value is: chooses the keyboard, autocorrection, obscured entry and semantics. code is text written for a machine rather than for a reader — a snippet of configuration, a pattern, an identifier, a query, a key: a left-to-right run in the code face that the platform does not correct, capitalise, check for spelling or complete, on one line or, with multiline, on many. date, time and date-time are entered segment by segment in the locale's numeric form and held as ISO 8601. The value is a string for every type. — Text input
value string "" The text in the field; for date, time and date-time, the ISO 8601 form of what its segments give (2026-09-27, 14:30, 2026-09-27T14:30), or empty while a segment is empty or the value is not available. — Text input
label string "" Names the control, visibly and to assistive technology. Required, unless the control names itself (a checkbox or a switch with its own label): then leave it empty and the field adds only help and an error. — Field Names the text input. In a filled or outlined look the input draws it, floating in its box, and the Field draws no label row (FLD-25, TXI-13).
labelHidden boolean false Does not draw the label, which stays the accessible name. For a control whose visible label is elsewhere (a table column, a toolbar). — Field Hides the label wherever it would be drawn: the Field's row, or the input's floating label (FLD-25).
placeholder string "" A hint shown while the value is empty and the label is not in the input line (above, floated or hidden). Never a replacement for label. In a code field it is an example of the code and is drawn as code (TXI-53, TXI-54); a hint in words belongs in the supportingText of its Field. Ignored by a date, time or date-time field, whose empty segments each show their own. — Text input
supportingText string "" Help shown below the control and given to assistive technology as the control's description. While the field is in error it gives way to errorText, unless the design system keeps both (comp.field.error-text.placement append). — Field
errorText string "" Shown while error is true, in supportingText's place — or below it, which then stays, where the design system's comp.field.error-text.placement is append: what is wrong and how to put it right, in words. — Field
error boolean false Puts the field in the error condition: the error text is drawn, the control is exposed as invalid and, where it has an error condition of its own, drawn in it. What a value means is the product's to validate; the field never sets this itself. — Field The Field puts its text input in the input's own error condition (FLD-8, TXI-11).
disabled boolean false Disables the control and draws the frame as disabled. Disable a framed control here, not on the control, so that its label follows. — Field The Field disables its text input (FLD-14, TXI-5); the text field is then left out of its form (TXF-63).
readOnly boolean false The value can be focused, selected and copied, but not changed. — Text input
required boolean false What the form asks of the control: true exposes it as required and draws the required mark beside the label. It never validates: a missing answer is the product's to report with error. — Field The Field exposes its text input as required (FLD-10, TXI-23).
optional boolean false Draws the optional tag after the label and adds optionalText to the control's description, because assistive technology has no state for it. Ignored while required is true: a control is never both. — Field
requiredMark auto | tag | asterisk | none "auto" How this control shows that it is required: tag is a small filled Tag with requiredText, asterisk is "*" after the label, none draws nothing and keeps the semantics. Auto takes comp.field.requirement.mark, which follows sys.behavior.requirement-mark, the system's one answer. Ignored unless required is true. — Field
requiredText string "" The text of the required tag, in the product's language ("Required", "必須"). The product's: Tanrei has no word for it. Empty reads the product's words; with neither, FLD-24 says what is drawn. — Field
optionalText string "" The text of the optional tag, in the product's language ("Optional", "任意"); also read to assistive technology as part of the description. The product's: Tanrei has no word for it. Empty reads the product's words. — Field
multiline boolean false Accepts line breaks and shows several lines, sized as resize says. Ignored for password, date, time and date-time. — Text input
minRows number 2 The fewest lines a multi-line field shows: the height of an empty auto-resized field, the starting and smallest height of a manually resized one, and the height of a fixed one. — Text input
maxRows number 0 The most lines an auto-resized field grows to before it scrolls, and the tallest a manually resized one can be made; 0 is no limit. Ignored while resize is fixed. — Text input
resize auto | manual | fixed "auto" How a multi-line field gets its height. auto: it follows the number of lines between minRows and maxRows, with an animated change, and has no handle. manual: it starts at minRows lines and the person resizes it with the handle in its bottom end corner. fixed: it stays at minRows lines, with no handle. Ignored unless multiline. — Text input
resizeAxis vertical | both "vertical" What the person can resize by hand: the height only, or the height and the width. Ignored unless resize is manual. — Text input
resizeLabel string "" The accessible name of the resize handle, in the product's language ("Resize"). The product's: Tanrei has no word for it. Empty reads the product's words. Required while the handle is shown. — Text input
wrap boolean true Whether a multi-line code field continues a line that does not fit on following rows (true), or keeps every line of the code on one row and scrolls sideways inside its container (false) — for code whose lines are read as rows: a table of values, a log, a file in which a line break means something. The value is the same either way. Ignored unless type is code and multiline is true: prose always wraps, and a single line always scrolls in its line. — Text input
maxLength number 0 The most user-perceived characters the user can enter; 0 is no limit. Ignored for date, time and date-time. — Text input
counter boolean false Keeps the character count (and maxLength, when set): part of the input's description and announced when typing pauses (TXI-19). The input draws none: a text field gives the count to its Field, which draws it at the end of its supporting row, and a product that frames the input by hand gives it to the Field's countText. — Text input The input keeps the count, the text field gives it to its Field, and the Field draws it (TXI-19, TXF-67, FLD-27).
counterText string "" The count said to assistive technology while counter is true and maxLength is greater than 0: the whole sentence in the product's language with three places the input fills — {count} with the characters entered, {limit} with maxLength and {remaining} with how many more can be entered ("{remaining} characters left", "{limit}文字中{count}文字"). Empty reads the product's words; with neither, the count is said as the digits it is drawn in ("12/40"). A sentence that is right for every number, or a prop set per count where the language needs another form for one. — Text input
counterNoLimitText string "" The count said to assistive technology while counter is true and maxLength is 0: the whole sentence with one place, {count}, filled with the characters entered ("{count} characters", "{count}文字"). Empty reads the product's words; with neither, the count is said as its digits. — Text input
prefixText string "" Fixed text before the value, not part of it. In a code field it is code: drawn in the code face at the left of the value in every layout direction (TXI-53). — Text input
suffixText string "" Fixed text after the value, not part of it. In a code field it is code: drawn in the code face at the right of the value in every layout direction (TXI-53). — Text input
autocomplete string "" The autofill hint, as an HTML autofill field name (email, current-password, new-password, one-time-code, tel, postal-code, …); each platform maps it to its own content type. A code field left without one asks the platform to offer no remembered entry (TXI-55). — Text input
locale string "" A BCP 47 language tag ("en-US", "ja-JP") for how a date, time or date-time field shows and reads its value: the order of the year, month and day segments, the separators, the clock, the digits and the names of the units. Empty uses the platform's current locale. The calendar is the Gregorian calendar in every locale. — Text input
min string "" The earliest value a date, time or date-time field accepts from the person, in its ISO 8601 form; empty for no limit. — Text input
max string "" The latest value a date, time or date-time field accepts from the person, in its ISO 8601 form; empty for no limit. — Text input
disabledDates strings Dates inside min and max that a date or date-time field does not accept from the person, as ISO 8601 calendar dates — the same list the product gives its DateTimePicker. Ignored by a time field. — Text input
invalidText string "" The words for a date, time or date-time input committed with some segments filled and others empty: given to the Field that frames it, which shows them as its error text (TXI-30). — Text input Shown by the Field, in its supporting row (FLD-28).
unavailableText string "" The words for a date, time or date-time input committed with a value before min, after max or, for a date, listed in disabledDates: given to the Field that frames it, which shows them as its error text (TXI-30). — Text input Shown by the Field, in its supporting row (FLD-28).
segmentEmptyText string "" What assistive technology reads as the value of a date or time segment that holds nothing yet, in the product's language ("Empty"). The product's: Tanrei has no word for it. Empty reads the product's words; with neither, an empty segment has no value text. — Text input
picker boolean false Shows the picker button in a date, time or date-time field. The field presents no picker itself: it emits pickerRequest, and the product presents a DateTimePicker in the matching mode and sets value to what was picked. — Text input
pickerLabel string "" The accessible name of the picker button ("Choose date"). Required while picker is shown. — Text input

插槽

插槽 说明
leading-icon An icon at the start of the input. — Text input
trailing-icon An icon at the end of the input, or the product's own icon-only Button (clear, reveal password). A date or time field's picker button comes after it. — Text input
label-icon A glyph before the label. — Field

事件

事件 说明
input The user changed the value; carries the new value. Emitted for every change, not when the product sets value. A date, time or date-time field emits it when its value changes — when its last segment is filled, a filled field is edited, or it is emptied — not for a segment that leaves the value empty. — Text input
change The user committed a changed value (focus left the field, or the action key was pressed in a single-line field); carries the value. — Text input
action The user pressed the keyboard's action key (Enter, Return, Go, Search, Done) in a single-line field. — Text input
pickerRequest The user activated the picker button of a date, time or date-time field; carries the value, type, min and max. The product presents the picker and sets value to the picked value. — Text input
entryError The user committed a date, time or date-time field that is half-filled or whose value is not available; carries the reason (invalid: some segments are empty; unavailable: before min, after max or a listed date) and the text of the row of segments as drawn. The entry error itself is shown by the Field that frames the input (TXI-30). — Text input
resizeEnd The person finished changing the size of a field resized by hand: a drag of its handle ended, or a key or an activation of the handle changed the size (TXI-69). Carries lines, the number of lines the input shows, and width, the field's width in pixels WHILE resizeAxis is both (null otherwise). Not emitted when a change of resize, resizeAxis, minRows or maxRows gives the size back. — Text input

状态

行为

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

ID 需求
TXF-62 THE text field SHALL be a Field that frames one text input, and nothing else. It SHALL give label, labelHidden, supportingText, errorText, error, disabled, required, optional, requiredMark, requiredText and optionalText to its Field and every other prop to its text input, each unchanged; SHALL hold the label-icon slot for its Field and the leading-icon and trailing-icon slots for its text input; and SHALL emit its text input's events as its own, each once. THE text field SHALL be as wide as the place it stands in, and its text input SHALL fill it: it is a block with no width of its own, as its Field is (FLD-1), around a control that fills the width it is given (TXI-63). It SHALL draw no part of its own, have no token of its own and hold no state of its own but the count it carries from one member to the other (TXF-67): what it shows, how it behaves and what it exposes to assistive technology SHALL be exactly what that Field and that text input do when a product places the one in the other (FLD-1 to FLD-28; TXI-1 to TXI-63), with the Field given the input's count — the label row and its requirement mark (FLD-2 to FLD-6), or the label that floats in the box with the Field's row left out (FLD-25, TXI-13, TXI-50); the supporting row, the error and the count (FLD-7 to FLD-9, FLD-26, FLD-27, TXI-19, TXF-67); the name, the description and the states (FLD-10, FLD-11, FLD-14, TXI-23); and a date's entry error shown in the Field's row (FLD-28, TXI-30).
TXF-63 WHERE the platform has forms (the web) THE text field SHALL be the one control the form sees: it SHALL take name, submit its text input's value, reset to the value it was first given, restore it on navigation, report to the form's constraint validation exactly what its text input would report there (TXI-23, TXI-30), and be left out WHILE disabled is true or a disabled group of the form holds it; its text input and its Field SHALL take no part in the form themselves. WHEN the action key asks for the form's submission (TXI-3) the form asked SHALL be the text field's own.
TXF-64 WHEN the text field is given focus — by the product, by a press on any part of it that FLD-12 or TXI-8 gives to the input, or by a form that reports its validity — THE text field SHALL pass the focus to its text input (in a date, time or date-time field, to the segment TXI-43 names) and SHALL add no stop to the focus order: the stops inside it are its text input's, in that input's order (an interactive slot element, the picker button, the resize handle). WHILE focus is anywhere inside it THE text field SHALL count as focused for its Field (FLD-13).
TXF-65 IF the text field is placed in a Field THE text field SHALL in debug builds report that it is a field already and that the bare control is the text input, and both SHALL still render (FLD-15). WHILE it stands in a field group THE text field SHALL follow that group's disabled, through its own Field, and take nothing else from the group (FGR-20).
TXF-66 THE text field SHALL have no words of its own and SHALL read none: requiredText and optionalText are read by its Field (FLD-24), resizeLabel and segmentEmptyText by its text input (TXI-52), each from the instance's prop and then from the product's words.
TXF-67 WHILE counter is true THE text field SHALL give its Field, as its count text (FLD-27), the count its text input keeps (TXI-19), as the input writes it, and SHALL give the new one in the same frame in which the value changes — by typing, by a paste, because the product set value or maxLength — without a motion and without an announcement of its own: the input describes and announces its count itself (TXI-19) and the Field keeps the drawn text from assistive technology (FLD-27). WHILE counter is false THE text field SHALL give its Field no count text. THE text field SHALL NOT count, format or write a digit itself.
TXF-68 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 text field 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 text field is invalid — except those the text field owns (aria-label, aria-labelledby, aria-disabled), which it SHALL move and not expose: its name is label (TXF-62). 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 text field SHALL take it off that element. WHEN the product sets an aria- attribute the text field owns THE text field SHALL report it in debug builds.

各平台名称对照

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

规格 Web SwiftUI Jetpack Compose Flutter React Native
组件 tanrei-text-field TanreiTextField TanreiTextField TanreiTextField TextField
variant · 属性 variant variant variant variant variant
size · 属性 size size size size size
type · 属性 type type type type type
value · 属性 value text value controller value
label · 属性 label label label labelText label
labelHidden · 属性 labelHidden labelsHidden labelHidden labelHidden labelHidden
placeholder · 属性 placeholder prompt placeholder hintText placeholder
supportingText · 属性 supportingText supportingText supportingText helperText supportingText
errorText · 属性 errorText errorText errorText errorText errorText
error · 属性 error error isError isError error
disabled · 属性 disabled disabled enabled enabled disabled
readOnly · 属性 readOnly readOnly readOnly readOnly readOnly
required · 属性 required isRequired required required required
optional · 属性 optional isOptional optional optional optional
requiredMark · 属性 requiredMark requiredMark requiredMark requiredMark requiredMark
requiredText · 属性 requiredText requiredText requiredText requiredText requiredText
optionalText · 属性 optionalText optionalText optionalText optionalText optionalText
multiline · 属性 multiline axis singleLine multiline multiline
minRows · 属性 minRows lineLimit minLines minLines minRows
maxRows · 属性 maxRows lineLimit maxLines maxLines maxRows
resize · 属性 resize resize resize resize resize
resizeAxis · 属性 resizeAxis resizeAxis resizeAxis resizeAxis resizeAxis
resizeLabel · 属性 resizeLabel resizeLabel resizeContentDescription resizeSemanticLabel resizeAccessibilityLabel
wrap · 属性 noWrap wrap softWrap softWrap wrap
maxLength · 属性 maxLength maxLength maxLength maxLength maxLength
counter · 属性 counter counter counter showCounter counter
counterText · 属性 counterText counterText counterText counterSemanticText counterAccessibilityText
counterNoLimitText · 属性 counterNoLimitText counterNoLimitText counterNoLimitText counterNoLimitSemanticText counterNoLimitAccessibilityText
prefixText · 属性 prefixText prefix prefix prefixText prefix
suffixText · 属性 suffixText suffix suffix suffixText suffix
autocomplete · 属性 autocomplete textContentType contentType autofillHints autoComplete
locale · 属性 locale locale locale locale locale
min · 属性 min min min min min
max · 属性 max max max max max
disabledDates · 属性 disabledDates disabledDates disabledDates disabledDates disabledDates
invalidText · 属性 invalidText invalidText invalidText invalidText invalidText
unavailableText · 属性 unavailableText unavailableText unavailableText unavailableText unavailableText
segmentEmptyText · 属性 segmentEmptyText segmentEmptyText segmentEmptyText segmentEmptyText segmentEmptyText
picker · 属性 picker onPickerRequest onPickerRequest onPickerRequest onPickerRequest
pickerLabel · 属性 pickerLabel pickerLabel pickerContentDescription pickerSemanticLabel pickerAccessibilityLabel
input · 事件 input text onValueChange onChanged onChangeText
change · 事件 change onCommit onCommit onCommit onEndEditing
action · 事件 action onSubmit keyboardActions onSubmitted onSubmitEditing
pickerRequest · 事件 pickerrequest onPickerRequest onPickerRequest onPickerRequest onPickerRequest
entryError · 事件 entryerror onEntryError onEntryError onEntryError onEntryError
resizeEnd · 事件 resizeend onResizeEnd onResizeEnd onResizeEnd onResizeEnd
备注 The tag is tanrei-text-field, the class TanreiTextField, the React name TextField. Members: <tanrei-field> holding one <tanrei-text-input> in its default slot; the product places none. The host is display: block, as wide as its place, and the input fills it (TXF-62). Value member: tanrei-text-input. The host is the form's element — name, the value and its state, validity anchored on the input, reset to the value attribute, restore, form, a disabled fieldset — and the input inside has no name. Props, set as properties on the member each is from. On the Field: label, labelHidden, supportingText, errorText, error, disabled, required, optional, requiredMark, requiredText, optionalText. On the text input: variant, size, type, value, placeholder, readOnly, multiline, minRows, maxRows, resize, resizeAxis, resizeLabel, noWrap (the inverse of wrap, attribute no-wrap), maxLength, counter, prefixText, suffixText, autocomplete, locale, min, max, disabledDates, invalidText, unavailableText, segmentEmptyText, picker, pickerLabel. Attributes are the members' own: kebab-case, except readonly, maxlength, autocomplete, min and max. Props of one name on both members — label, labelHidden, error, disabled — are the Field's; the input's are never set by the element and reach the input through the connection. Events: input, change, action, pickerrequest, entryerror are the text input's own composed events and leave by themselves, with the host as their target and value already new; action stays cancelable. None is stopped and none is copied. Slots: label-icon is passed to the Field, leading-icon and trailing-icon to the text input, each by a slot element of the same name rendered only while the host has a child for it; the product's own Button in trailing-icon stays the product's element. Parts: the member hosts are field and text-input. Exported from the Field: label, label-icon, requirement-label, supporting-text, counter. Exported from the text input: container, outline, active-indicator, label, requirement-label, input, segment, prefix, suffix, leading-icon, trailing-icon, picker-button, resize-handle. Two names collide, label and requirement-label. Each is exported with its member's name before it, always (field-label, text-input-label, field-requirement-label, text-input-requirement-label), and under the bare name from both members as well, because the two are one thing to a person and are never drawn at once: FLD-25 is the sentence that holds it — while its control draws the label itself the Field draws no label row and no requirement tag, and a stacked input draws neither (TXI-13, TXI-50). So tanrei-text-field::part(label) keeps meaning the drawn label. Focus: the shadow root does not delegate. focus() and blur() call the text input's; a press on the label row — the label, its glyph, the requirement mark — focuses the input by FLD-12, the Field's own sentence (the Field calls focus() on its control, which stands beside it in the same tree); a press on the box, and on a label that floats in it, by TXI-8; and a press on the supporting row — the help, the error text, the count — or on the gap between a row and the box does nothing (FLD-12). The connection is the Field's with its control, in both directions (FLD-25, FLD-26, FLD-28, TXI-62); the element takes no part in it. A member's prop it sets itself (fills): the Field's countText, from the text input's read-only countText after each of the input's updates, in the same task (TXF-67). Parts and tokens of its own: none. The element writes no custom property: --tanrei-comp-field-*, --tanrei-comp-input-box-* and --tanrei-comp-text-input-* reach the members by inheritance, and one text field is set apart by setting any of them on the element. What its twin cannot do that it does: keep the Field's count text equal to the input's count as the person types (TXF-67); nothing else. The twin: <tanrei-field label="Email" supporting-text="We never share your address." required><tanrei-text-input name="email" type="email" autocomplete="email"></tanrei-text-input></tanrei-field>. Its Field draws the help and the error text together (FLD-7, FLD-31), and the five fixtures *-supporting-error* hold the pair to its twin. Lagging: this platform draws the text field as one view that draws its own frame, written against the token group comp.text-field, which the tokens do not have. It owes, first, its sources moved to the generated names of comp.input-box, comp.text-input and comp.field: they do not compile until then. Then the split: the text input as a view of its own, and the text field as the Field around it. Its names and its props stay. What it does today: text: Binding<String> (as TextField); every edit writes the binding, which is the input event. password draws a SecureField; multiline is TextField(axis: .vertical) with .lineLimit(minRows...maxRows) — not TextEditor, which has no prompt and does not grow. labelHidden follows the .labelsHidden() environment; disabled follows .disabled(_:); action is the onSubmit: parameter, called after the commit (TXI-3); an enclosing .onSubmit modifier still fires, but before the commit, because SwiftUI runs the outer handler first. onCommit is Tanrei's own parameter (SwiftUI deprecated its own), called when focus leaves with a changed value. type maps to .keyboardType, .textInputAutocapitalization, .autocorrectionDisabled and .submitLabel; autocomplete maps HTML names to UITextContentType. locale follows the \.locale environment when not given. The picker button is shown when an onPickerRequest: closure is given (a nil closure is picker false); the product presents the picker and writes the binding. Not built on this platform yet: the optional flag (optional) beside the required flag; the resize modes and the handle (TXI-37–TXI-41, TXI-69), the segmented date and time row (TXI-42–TXI-47) and the requirement label (FLD-4, FLD-5, TXI-50). The platform proposes its own idiom for the segments when it catches up — each unit focusable and adjustable on its own, with the same value and events. Not built on this platform yet: the words props have no defaults, and the product's words are read from the \.tanreiWords environment value. Not built on this platform yet, for type code: .autocorrectionDisabled(), .textInputAutocapitalization(.never), .keyboardType(.asciiCapable), a monospaced design of the input's size, and .environment(\.layoutDirection, .leftToRight) on the input row alone; smart quotes, smart dashes and smart insert and delete are traits of the UIKit and AppKit text view (smartQuotesType, smartDashesType, smartInsertDeleteType, isAutomaticQuoteSubstitutionEnabled), so a code field is drawn on that view where SwiftUI's TextField gives no access to them; wrap: false needs that view too (a text container that does not track the view's width, in a scroll view), because TextField(axis: .vertical) always wraps. Not built on this platform yet: the count said as the product's sentence (counterText, counterNoLimitText, TXI-19); today the digits are said. Not built on this platform yet: comp.field.error-text.placement — the error text takes the help's place under every system, which is replace, the default; append, which keeps the help and draws the error text below it, is not drawn (FLD-7, FLD-31). Not built on this platform yet: size small (40), and in a right-to-left layout the value of an always left-to-right type beside its prefix (TXI-67). Lagging: this platform draws the text field as one view that draws its own frame, written against the token group comp.text-field, which the tokens do not have. It owes, first, its sources moved to the generated names of comp.input-box, comp.text-input and comp.field: they do not compile until then. Then the split: the text input as a view of its own, and the text field as the Field around it. Its names and its props stay. What it does today: Compose idiom (as TextField): value / onValueChange, with a TextFieldValue overload for products that manage selection and composition themselves; enabled and singleLine are inverse flags; isError, readOnly, minLines, maxLines, prefix and suffix keep Compose's names. Built on BasicTextField. type maps to KeyboardOptions (keyboard type, capitalization, autocorrect, IME action) and, for password, PasswordVisualTransformation; autocomplete maps to ContentType semantics. onCommit is Tanrei's own callback, called when focus leaves with a changed value or the IME action commits. locale defaults to the current configuration's locale. The picker button is shown when onPickerRequest is not null (null is picker false); pickerContentDescription follows Compose's naming for an icon's accessible name. Not built on this platform yet: the optional flag (optional) beside the required flag; the resize modes and the handle (TXI-37–TXI-41, TXI-69), the segmented date and time row (TXI-42–TXI-47) and the requirement label (FLD-4, FLD-5, TXI-50). The platform proposes its own idiom for the segments when it catches up — each unit focusable and adjustable on its own, with the same value and events. Not built on this platform yet: the words props have no defaults, and the product's words are read from LocalTanreiWords. Not built on this platform yet, for type code: KeyboardOptions(capitalization = KeyboardCapitalization.None, autoCorrectEnabled = false, keyboardType = KeyboardType.Ascii), TextStyle(textDirection = TextDirection.Ltr, textAlign = TextAlign.Left, fontFamily = …, fontFeatureSettings = "liga 0") on the input row inside CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr); softWrap = false puts the BasicTextField in a horizontalScroll that follows the cursor's rectangle. Not built on this platform yet: the count said as the product's sentence (counterText, counterNoLimitText, TXI-19); today the digits are said. Not built on this platform yet: comp.field.error-text.placement — the error text takes the help's place under every system, which is replace, the default; append, which keeps the help and draws the error text below it, is not drawn (FLD-7, FLD-31). Not built on this platform yet: size small (40), and in a right-to-left layout the value of an always left-to-right type beside its prefix (TXI-67). Lagging: this platform draws the text field as one view that draws its own frame, written against the token group comp.text-field, which the tokens do not have. It owes, first, its sources moved to the generated names of comp.input-box, comp.text-input and comp.field: they do not compile until then. Then the split: the text input as a view of its own, and the text field as the Field around it. Its names and its props stay. What it does today: Flutter idiom (as TextField and InputDecoration): the value lives in a TextEditingController (or an internal one with initialValue); labelText, hintText, helperText, errorText, prefixText, suffixText, minLines, maxLines, maxLength, readOnly, enabled and autofillHints keep Flutter's names, enabled being the inverse flag. isRequired and showCounter avoid Dart's required keyword and Flutter's own counter, which Tanrei replaces (it counts grapheme clusters with characters). maxRows: 0 is maxLines: null. type maps to keyboardType, obscureText, autocorrect, textCapitalization and textInputAction. onCommit is Tanrei's own callback, called when focus leaves with a changed value. locale defaults to Localizations.localeOf(context). The picker button is shown when onPickerRequest is not null (null is picker false). Not built on this platform yet: the optional flag (optional) beside the required flag; the resize modes and the handle (TXI-37–TXI-41, TXI-69), the segmented date and time row (TXI-42–TXI-47) and the requirement label (FLD-4, FLD-5, TXI-50). The platform proposes its own idiom for the segments when it catches up — each unit focusable and adjustable on its own, with the same value and events. Not built on this platform yet: the words props have no defaults, and the product's words are read from the TanreiWords inherited widget, which the app fills from its own localizations. Not built on this platform yet, for type code: autocorrect: false, enableSuggestions: false, textCapitalization: TextCapitalization.none, smartDashesType: SmartDashesType.disabled, smartQuotesType: SmartQuotesType.disabled, spellCheckConfiguration: const SpellCheckConfiguration.disabled(), textDirection: TextDirection.ltr, textAlign: TextAlign.left, a TextStyle with the code family and fontFeatures: [FontFeature.disable('liga')], the input row in a Directionality(textDirection: TextDirection.ltr); softWrap: false is an EditableText of unbounded width in a horizontal Scrollable that keeps the caret in view, because a multi-line TextField always wraps. Not built on this platform yet: helperText stays while the field is in error and errorText is drawn below it (FLD-7, FLD-31), unlike InputDecoration; today the error text takes the help's place. Not built on this platform yet: the count said as the product's sentence (counterText, counterNoLimitText, TXI-19); today the digits are said. Not built on this platform yet: comp.field.error-text.placement (FLD-7, FLD-31), size small (40), and in a right-to-left layout the value of an always left-to-right type beside its prefix (TXI-67). Lagging: this platform draws the text field as one view that draws its own frame, written against the token group comp.text-field, which the tokens do not have. It owes, first, its sources moved to the generated names of comp.input-box, comp.text-input and comp.field: they do not compile until then. Then the split: the text input as a view of its own, and the text field as the Field around it. Its names and its props stay. What it does today: React Native idiom (as TextInput): value / onChangeText, onEndEditing for a commit, onSubmitEditing for the action key; multiline, readOnly, maxLength and autoComplete keep TextInput's names. type maps to keyboardType, secureTextEntry, autoCapitalize, autoCorrect, inputMode and returnKeyType. A controlled value echoed back during a composition must not reset it (TXI-4). React Native gives JavaScript no signal that a composition is in progress, so the component cannot defer a write the product makes mid-composition; echoing the value back is safe. For date, time and date-time, onChangeText receives the value (ISO 8601 or empty), not the text as typed (TXI-29). The picker button is shown when onPickerRequest is given (undefined is picker false). Locale data needs Intl with formatToParts, which Hermes has only in part. Not built on this platform yet: the optional flag (optional) beside the required flag; the resize modes and the handle (TXI-37–TXI-41, TXI-69), the segmented date and time row (TXI-42–TXI-47) and the requirement label (FLD-4, FLD-5, TXI-50). The platform proposes its own idiom for the segments when it catches up — each unit focusable and adjustable on its own, with the same value and events. Not built on this platform yet: the words props have no defaults, and the product's words are read from the TanreiWordsProvider context. Not built on this platform yet, for type code: autoCorrect={false}, autoCapitalize="none", spellCheck={false}, autoComplete="off", smartInsertDelete={false}, keyboardType="ascii-capable" on iOS and "visible-password" on Android only where suggestions cannot be turned off otherwise, style={{ writingDirection: 'ltr', textAlign: 'left', fontFamily: … }}, the input row with direction: 'ltr'. TextInput has no multi-line form that does not wrap: wrap={false} needs a native text view, and until one exists the field wraps and its README says so. Not built on this platform yet: the count said as the product's sentence (counterText, counterNoLimitText, TXI-19); today the digits are said. Not built on this platform yet: comp.field.error-text.placement — the error text takes the help's place under every system, which is replace, the default; append, which keeps the help and draws the error text below it, is not drawn (FLD-7, FLD-31). Not built on this platform yet: size small (40), and in a right-to-left layout the value of an always left-to-right type beside its prefix (TXI-67).

令牌

令牌 引用 解析值(浅色)
--tanrei-comp-field-counter-gap sys.space.space200 16px
--tanrei-comp-field-error-text-gap sys.space.space50 4px
--tanrei-comp-field-error-text-placement — 0
--tanrei-comp-field-label-icon-gap sys.space.space100 8px
--tanrei-comp-field-label-icon-size sys.icon.small 20px
--tanrei-comp-field-label-gap sys.space.space100 8px
--tanrei-comp-field-motion-state-duration sys.motion.role.state.default 250ms
--tanrei-comp-field-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-field-requirement-asterisk-gap sys.space.space25 2px
--tanrei-comp-field-requirement-gap sys.space.space100 8px
--tanrei-comp-field-requirement-mark sys.behavior.requirement-mark 0
--tanrei-comp-field-standard-counter-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-standard-disabled-counter-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-text-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-counter-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-label-icon-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-label-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-required-asterisk-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-supporting-text-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-value-text-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-label-icon-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-required-asterisk-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-supporting-text-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-value-text-color — #1b1b1b · 38%
--tanrei-comp-field-standard-error-text-color sys.color.error #cd262f
--tanrei-comp-field-standard-error-counter-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-standard-error-label-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-standard-error-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-field-standard-error-required-asterisk-color sys.color.primary #1b1b1b
--tanrei-comp-field-standard-error-supporting-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-standard-error-value-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-standard-focused-counter-color — #4b4b4b
--tanrei-comp-field-standard-focused-error-text-color — #cd262f
--tanrei-comp-field-standard-focused-error-counter-color — #4b4b4b
--tanrei-comp-field-standard-focused-error-label-icon-color — #4b4b4b
--tanrei-comp-field-standard-focused-error-label-color — #1b1b1b
--tanrei-comp-field-standard-focused-error-required-asterisk-color — #1b1b1b
--tanrei-comp-field-standard-focused-error-supporting-text-color — #4b4b4b
--tanrei-comp-field-standard-focused-error-value-text-color — #4b4b4b
--tanrei-comp-field-standard-focused-label-icon-color — #4b4b4b
--tanrei-comp-field-standard-focused-label-color — #1b1b1b
--tanrei-comp-field-standard-focused-required-asterisk-color — #1b1b1b
--tanrei-comp-field-standard-focused-supporting-text-color — #4b4b4b
--tanrei-comp-field-standard-focused-value-text-color — #4b4b4b
--tanrei-comp-field-standard-label-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-standard-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-field-standard-required-asterisk-color sys.color.primary #1b1b1b
--tanrei-comp-field-standard-supporting-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-standard-value-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-supporting-text-gap sys.space.space50 4px
--tanrei-comp-field-value-text-gap sys.space.space200 16px
--tanrei-comp-field-label-typescale sys.typescale.label 500 14px/1.4286
--tanrei-comp-field-supporting-text-typescale sys.typescale.label-small 500 12px/1.3333
--tanrei-comp-field-value-text-typescale sys.typescale.label 500 14px/1.4286

测试样例

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

生成自 specs/text-field.spec.json, tokens/comp/field.tokens.json, design/tokens/

Web 上绘制方式的不同之处

  • 这个元素的 shadow root 里有一个 <tanrei-field>,其中放着一个 <tanrei-text-input>,此外什么都没有。导入这个元素,两个成员也随之完成定义。
  • 这个元素是一个块,会填满所在的地方,而不是一个和浏览器自己的文本输入框一样宽的行内方框。
  • 这个元素是与表单关联的元素。它接受 name 和 form,提交它的值——禁用时什么都不提交——重置为 value 属性(attribute)的值,在导航时恢复,跟随被禁用的 <fieldset>;设置了 required 而它为空时,报告缺少值。它的两个成员不向表单添加任何条目。在单行的字段里按 Enter 会按下元素所在表单的默认按钮;取消 action 可以阻止这一点。
  • input、change、action、pickerrequest 和 entryerror 是文本输入框自己的事件。它们的目标是这个元素,在每个监听器里 event.target.value 都是新的值。detail 的类型是输入框的:TanreiTextInputActionDetail、TanreiTextInputPickerRequestDetail 和 TanreiTextInputEntryErrorDetail。
  • 部件:field 和 text-input 是两个成员。成员自己的部件在这里的名称与下一层相同:label-icon、supporting-text、counter;container、outline、active-indicator、input、segment、prefix、suffix、leading-icon、trailing-icon、picker-button、resize-handle。label 和 requirement-label 是画出来的那一个,无论由哪个成员绘制;field-label、text-input-label、field-requirement-label 和 text-input-requirement-label 指的是某一个成员的。
  • 这个元素自己不能获得焦点,也没有角色:focus() 会把焦点交给输入框。按标签行、方框或浮动标签,都会让输入框获得焦点。按帮助文本、错误文本、计数,或者某一行与方框之间的空隙,什么都不会发生。
  • 用 label 为它命名——不该绘制标签的地方再加上 label-hidden——绝不要用 aria-label,它在这里不为任何东西命名。
  • 属性(attribute)就是成员自己的那些:用 kebab-case 书写,例如 supporting-text、error-text、min-rows 和 no-wrap,只有 readonly、maxlength、autocomplete、min 和 max 例外。没有 required 属性(attribute):请写 required。
  • 在元素定义之前,它没有尺寸。请预留高度。

指南

何时使用

当用户要键入一个值时,使用文本字段:姓名、电子邮件地址、搜索词、消息。当用户是从一组已知的值里挑选时,请使用专为选择而设计的控件。标签在别处、因而只要方框的地方——工具栏里的筛选框、表格的单元格——请使用文本输入框。

如何选择变体和尺寸

一个表单从头到尾使用同一种变体。

变体 特征 用在哪里
Stacked(默认) 标签位于一个没有边线的着色方框上方。聚焦时在方框边缘以内画一条线,不会挪动任何东西;出错时,静止状态下画一条细线。 所有系统。它是 Tanrei 系统提供的唯一变体。
Filled 一个着色的方框和一条底线,标签浮在里面 Material 和 cupertino 预设
Outlined 一圈聚焦时变粗的边框,标签浮到边框上 Material 和 cupertino 预设

在所有系统里,包括各个预设,默认都是 stacked。想要 Material 式浮动标签的 Material 产品,可以把文本字段的 variant 设为 outlined 或 filled。在 Tanrei 系统下,filled 和 outlined 仍然可用,但开发时会给出警告。

medium(40)是紧凑的文本字段,large(56)是标准的文本字段。筛选条件和密集的设置界面使用 medium,登录和结账表单使用 large。small(32)是小号下拉选择框的高度,用于应用栏、工具栏或一排紧凑控件中的文本字段,此时输入文本为 14。在默认系统中,另外两种尺寸的输入文本都是 16。高度是最小值:文本放大时,文本字段会随之增高,点击区域也始终满足最小目标尺寸。

标签和占位符

  • 每个文本字段都需要 label。在 stacked 文本字段中,它位于方框上方,从起始边开始,绝不移动,也不改变大小;标签太长时会换行,而不是被截断。label-icon 插槽可以在它前面放一个图标。在 filled 或 outlined 文本字段中,文本字段为空时标签停在文本字段里,聚焦或已有内容时则浮起。
  • labelHidden 不再绘制标签,但它仍然是无障碍名称,帮助文本和错误也仍然位于方框下方。只要方框的地方——工具栏里的筛选框、单元格、标签在旁边的一行——请改用带有自己 label 的文本输入框。
  • 占位符是一条提示,例如一个示例值。它在文本字段为空、并且标签不在输入行里时显示:在 stacked 文本字段中始终如此,在其他变体中则只在标签浮起或隐藏时如此。它绝不是名称。

必填和选填

required 和 optional 说明表单对一个文本字段的要求:required 表示必须填写,optional 表示可以不填。一个文本字段不可能既必填又选填:两者都设置时它是必填的。

  • 必填会把文本字段暴露为必填,并在标签旁边绘制必填标记。
  • 选填会绘制选填小标签,并把它的文字加入文本字段的描述。
  • 标记默认是 stacked 标签后面的一个小标签:必填用 filled 样式,选填用 tonal 样式。它的文字是 requiredText 和 optionalText,它们是你的:写在文本字段上,或者用 setWords() 为整个产品只给一次。Tanrei 不自带任何文字:没有 requiredText 时,小标签的位置上是一个星号;没有 optionalText 时,不绘制小标签。它不会让标签所在的行变高;标签没有给它留出空间时,它会换到单独的一行。
  • 改用星号。 必填的文本字段绘制哪种标记,是设计系统为所有表单做的一项选择,即 sys.behavior.requirement-mark:默认系统中是小标签,Material 预设中是星号,cupertino 中什么都不画。requiredMark 可以为单个文本字段覆盖它(tag、asterisk 或 none;none 保留语义,但什么都不画)。filled 或 outlined 文本字段在要求绘制小标签的地方绘制星号。
  • 浮动标签中的选填。 filled 或 outlined 文本字段在浮动的标签旁边没有放选填小标签的位置:它改为在标签后面以一个词的形式绘制 optionalText(“Company Optional”“公司名称(选填)”),随标签一起静止和浮动。按照你的表单的写法来写这个词,带不带括号都可以。
  • 它不做任何校验。 文本字段绝不会因为缺少值而设置 error。这要由你用 error 和 errorText 来做。

在一个表单里,标记两者中较少的那一种:大多数文本字段必填时,标记选填的;反之亦然。如果使用星号——filled 或 outlined 文本字段对必填字段总是使用星号——请在表单中靠近顶部的地方说明一次它的含义(“* 必填”):星号是一种惯例,而不是文字,并非人人都知道。

必填的文本字段会显示一个标记:不想要标记的地方,把 requiredMark 设为 none。

帮助文本和错误文本

  • supportingText 是文本字段下方的一行帮助文本。它也会作为文本字段的描述被朗读。
  • 校验由你负责。值有误时,设置 error 并编写 errorText:文本字段处于错误状态时,它取代辅助文本的位置,所以处于错误状态的表单不会比平时更高。帮助文本里仍然重要的内容,要在错误文本中再说一遍。希望帮助文本始终可见的设计系统,把 comp.field.error-text.placement 设为 append:这时错误文本绘制在帮助文本的下方,文本字段处于错误状态时会高出一行(参见字段)。
  • 要说明如何改正这个值,而不只是说它错了:写“请输入完整的邮箱地址。”,而不是“无效”。
  • 在用户离开文本字段或提交表单之后才显示错误,不要在用户还在输入时显示。

计数器

  • maxLength 限制用户可以输入的内容。它按用户看到的字符来计数,所以一个表情符号或一个带重音的字母只算一个。粘贴的内容会被截短到刚好放得下。你自己设置的值绝不会被截短。
  • counter 在辅助文本那一行的末尾显示计数;设置了 maxLength 时,后面还跟着上限(“12/40”)。
  • 只有当上限对用户有意义时才显示计数器,例如显示名称或简短的个人简介。

输入类型

type 决定键盘、自动更正和是否遮蔽输入。无论哪种类型,值都是字符串。

类型 键盘和行为
text 通用键盘。
email 电子邮件键盘,不自动大写,也不自动更正。
password 遮蔽的安全输入,不自动更正。始终为单行。
number 带小数分隔符的数字键盘。值不会被解析、取整或校验。
search 通用键盘,操作键显示为搜索。
tel 电话拨号键盘。
url URL 键盘,不自动大写,也不自动更正。
code 通用键盘,并且不对文本做任何处理:不自动更正,不自动大写,不做拼写检查,也不替换字符。单行,或者在设置 multiline 时为多行。参见代码。
date、time、date-time 日期、时间,或两者兼有,按区域设置的数字格式逐个单位输入。始终为单行。参见日期和时间。

加上 autocomplete,并给它一个 HTML 自动填充名称(email、current-password、one-time-code 等),平台就能填写这个文本字段。每个平台会把它映射为自己的内容类型。长度已知、按格子绘制的简短验证码,应使用一次性验证码输入框。

多行

multiline 接受换行,并显示多行。resize 说明文本字段的高度从何而来:

resize 高度 调整手柄 minRows 和 maxRows
auto(默认) 跟随行数,带一段短促的动效 无 最少和最多的行数。超过最多行数后滚动;0 表示没有限制
manual 由用户决定。初始为 minRows 位于末尾边的底角 能调到的最小和最大高度
fixed minRows 行 无 maxRows 被忽略
  • Auto 适合大多数文本字段:评论、消息、描述。长文本不应把表单的其余部分挤开时,设置 maxRows。
  • Manual 适合用户会回头阅读和整理的文本:备注、长描述、代码。resizeAxis 默认为 vertical;设为 both 时,用户还可以在布局允许的范围内改变文本字段的宽度。调整后的大小会保持下来,文本在其中换行和滚动。
  • Fixed 适合布局不能变动的表单,例如地址块。
  • 调整手柄不靠拖动也能用。 它是位于末尾边底角的小尺寸调整手柄,在指针移到文本字段上或文本字段获得焦点之前不显示;它也是一个焦点停靠点,由 resizeLabel 命名。方向键每次调整一行,Page Up 和 Page Down 每次调整 minRows 行,Home 和 End 调到最小和最大高度;点击、Enter 或 Space 让高度贴合文本,如果已经贴合,则回到 minRows。resizeAxis 为 both 时,同一次点击还会让文本字段与所在位置一样宽,再点一次则恢复原来的宽度,所以用指针不拖动也能改变宽度。它的目标是最小目标,尽管它的线条很小。用户改变大小之后,文本字段会触发 resizeend,其中带有它显示的行数(在两个方向都能调整时,还有它的宽度),方便你保存用户选定的大小。禁用的文本字段没有调整手柄;只读的文本字段保留它,因为阅读时可能需要更多空间。
  • 文本上方的东西都不会动。 多行文本字段的第一行就在单行文本字段绘制其文本行的位置,它的图标、前缀和后缀都以这一行为准居中。其余各行都排在下面,所以高度变化时,标签和第一行保持原位。

代码

type="code" 用于写给机器、而不是写给读者的文本:匹配模式、标识符、密钥、查询语句、一小段 JSON 或 YAML。它还是同一个文本字段——标签、帮助文本、错误、计数器、尺寸和在表单中的行为都一样——只是输入行会按代码写下的样子原样保存它。

  • 在任何语言里都从左到右。 值、它的插入点、占位符、前缀和后缀排成从左到右的一行,从左侧开始;在从右到左的页面里也是如此,也不管代码的第一个字母是什么:空的文本字段、只有一个 { 的文本字段,以及第一个字符串是阿拉伯文的文本字段,布局都一样。其余的一切跟随页面:标签、图标、帮助文本或错误文本、计数器和调整手柄。用从右到左的文字写的字符串或注释,只在它所在的行内排布,不会打乱周围任何东西的顺序。
  • 代码字体,字号与输入文本相同。 字体族是代码角色的;字号和行高仍然是输入文本的,所以代码字段和旁边的文本字段一样高。每个空格都会保留,制表符会对齐到下一个制表位,任何两个字符都不会合成连字。不会按含义给任何内容着色。
  • 不做改动。 文本字段会要求平台不要更正、大写、检查拼写、翻译或补全这个值,也不要把直引号换成弯引号。粘贴进来的是纯文本,一个字符都不变。值绝不会被去掉首尾空白、解析或校验:它的格式是否正确,要由你用 error 和 errorText 来说明。
  • 单行或多行。 单行适合匹配模式或密钥。设置 multiline 后,默认情况下,放不下的代码行会接着排到后面的行上;值里只有代码本身的换行。
  • 不换行的代码行。 wrap 为 false(在 Web 上是 no-wrap)时,代码的每一行都保持在字段的一行里,适合数值表、日志,或者换行本身有含义的文件。长的行在文本字段内部横向滚动,不会把任何东西撑宽。只有多行的代码字段才读取 wrap:普通文本始终换行,单行则始终在自己的行内滚动。
  • Tab 移动焦点。 没有任何按键改变含义:Tab 绝不缩进,Enter 不添加缩进,也不会替你补上右括号。制表符只能通过粘贴或由你的产品写入,并且会保留下来。
  • 它不是编辑器。 没有语法着色,没有行号,没有折叠,也没有逐行的错误标记。只展示、不键入的代码,请使用代码块;需要编辑器时,请嵌入你的产品选定的那一个。
  • 占位符是一段代码示例,按代码绘制。用文字写的提示应放在 supportingText 里,文本字段也在那里说明它期望什么(“JSON”)。

在从右到左的文本字段里,代码值从左侧开始,而 email、url、tel、number 或 password 的值虽然从左到右书写,却位于文本字段的起始边,也就是右侧。其他平台还没有实现代码类型。

日期和时间

date、time 或 date-time 文本字段是输入行里的一排分段,每个单位一个:月、日和年,时、分,以及 12 小时制下的上午/下午。它们按区域设置的顺序排列,并使用该区域设置的分隔符。文本字段以 ISO 8601 格式保存值,不带时区。

类型 值 en-US 中的一行 ja-JP 中的一行
date 2026-09-27 09/27/2026 2026/09/27
time 14:30 2:30 PM 14:30
date-time 2026-09-27T14:30 09/27/2026, 2:30 PM 2026/09/27 14:30
  • 区域设置。 locale(未设置时为平台的区域设置)决定顺序、分隔符、时制和数字。空的分段显示它自己的占位符(“mm”“dd”“yyyy”),所以这一行始终显示出它的格式。每个分段的宽度取它的占位符和它最宽的值两者中较宽的那个,所以输入或清空时这一行都不会移动。这些类型会忽略 placeholder、prefixText、suffixText、maxLength 和 multiline。
  • 移动。 Tab 依次经过每个分段,然后是选择器按钮。无论布局方向如何,左方向键和右方向键都移到相应一侧的分段:这一行是从左到右书写的,就像用数字写的日期一样。按下某个分段会让它获得焦点。获得焦点的分段画成选中的文本,没有插入点。
  • 步进。 上方向键和下方向键把分段的值加一或减一,到头后循环;在空的分段上,它们从今天的日期或当前时间开始。Page Up 和 Page Down 的步幅更大,Home 和 End 移到范围的两端。
  • 键入。 数字填入分段;当再输入任何数字都无法延续它,或者键入了分隔符时,焦点移到下一个分段。Backspace 删除一位数字,在空的分段上则退回上一个分段。年份需要四位数字。不存在的日期无法输入:新选的月份没有那么多天时,日会变成该月的最后一天。
  • 粘贴。 把完整的日期或时间粘贴到任意一个分段上,都会填满整行。它按区域设置的顺序被读作三组数字,而第一组如果是四位数字,就读作年份,所以粘贴 ISO 格式的日期在任何地方都有效。复制和剪切针对的是整行。
  • 值在每个分段都填好、并且结果可用之前一直为空。值发生变化时触发 input——最后一个分段填好了、已填满的一行被编辑了,或者被清空了——而让值保持为空的分段变化不触发。当焦点离开文本字段的分段和选择器按钮,或者按下操作键,并且值已改变时,触发 change。在分段之间移动不提交任何东西。
  • 输入错误。 只填了一半就离开的文本字段显示 invalidText。离开时值早于 min、晚于 max 或列在 disabledDates 中的文本字段显示 unavailableText。两者都画成错误,并触发带有原因(invalid 或 unavailable)的 entryError;下一次编辑会结束它。这是文本字段唯一会自己设置的错误,它不会动你的 error 属性。
  • 你设置的值会原样显示,绝不校验,也不触发任何事件。

文本字段的时制就是它的区域设置的时制:它不像日期时间选择器那样有 12 或 24 小时制的设置,也没有分钟步长。

选择器按钮。 设置 picker 后,文本字段在末尾绘制一个纯图标的 Button,日期用 calendar_month,时间用 schedule,由 pickerLabel 命名。激活它会触发 pickerRequest,其中带有值、类型、min 和 max;只填了一半的一行会发送空值,并且不引发输入错误,因为选择器马上就会给出一个完整的值。文本字段自己不呈现任何东西:由你的产品以对应的模式打开 DateTimePicker(停靠式的要锚定到文本字段上),并根据选择器的 change 设置文本字段的 value。给选择器设置与文本字段相同的 min、max 和 disabledDates。选择器关闭时,焦点回到这个按钮。文本字段禁用时按钮也禁用,文本字段只读时则不绘制按钮。

推荐

  • 把不属于值的固定文本放进 prefixText 和 suffixText:货币符号、单位。
  • 清除或显示密码之类的操作,以你自己的纯图标的 Button 放进后置插槽,并给它自己的名称。选择器按钮是文本字段唯一内建的操作,它排在你的按钮之后。
  • 可能写到好几行的文本使用 multiline。默认情况下,它从 minRows 增高到 maxRows,之后滚动。
  • 在表单要求填写的文本字段上设置 required,并且在提交表单时,仍然要在 errorText 中说明缺了什么。
  • 由机器读取的值——匹配模式、密钥、一小段配置——使用 type="code",并在标签和 supportingText 里说明它必须是什么。

避免

  • 不要把占位符当作标签。用户一开始输入,它就消失了。
  • 不要把 number 用于不是数量的值,例如卡号或邮政编码。请使用 text 并配上合适的 autocomplete。
  • 不要在一个表单里混用不同的变体。
  • 不要把文本字段放进字段。它已经是一个字段;调试构建会报告这一点。字段框住的控件是文本输入框。
  • 不要用 text 文本字段加上自己的解析来处理日期。请使用 date,这样区域设置的顺序、分段的按键和输入错误都是现成的。
  • 不要在设置 required 的同时,还在标签里写“(必填)”。标记已经说明了这一点,屏幕阅读器会读两遍。
  • 不要让手动调整大小的文本字段成为看到长文本的唯一途径。给它足够的 minRows,让它一出现就好用。
  • 不要仅仅为了显示一个不能更改的值而禁用文本字段。请使用 readOnly,这样用户仍然可以聚焦、选择并复制它。
  • 不要用 ::part(input) 或你自己的样式从外部给文本字段设置代码字体,也不要给这个元素加上 dir 或 spellcheck。请使用 type="code":只换字体的话,文本字段看起来没问题,却仍然会更正键入的内容。
  • 不要把代码字段当作编辑器,也不要用禁用的代码字段来展示长代码:禁用的文本字段没有可以用来滚动的插入点。请使用 readOnly,或者代码块。

无障碍

角色
textbox
标注
The text field adds nothing to what its members expose, and the one thing it coordinates — the drawn count (TXF-67) — is kept from assistive technology, which hears the count from the input: the Field gives the text input its name, its description and its required, invalid and disabled states (FLD-8, FLD-10, FLD-11, FLD-14), and the text input carries them on its own element (TXI-23). Assistive technology meets one text box — or, for a date or a time, one group of spin buttons — named by label, exactly as it meets a Field around a text input written by hand.
最小目标
即使外观尺寸更小,点击区域也满足最小目标尺寸。 minimum 32px · pointer 44px · spacing 8px · touch 48px

键盘操作

按键 动作
Every key The text input's (its accessibility block; TXI-3, TXI-41, TXI-43 to TXI-45, TXI-59). The text field handles no key and adds no focus stop (TXF-64).

备注

  • One level down is the same thing: a Field with a text input in it. Every fixture has that pair as its twin, and the accessibility tree of the two is the same (TXF-62). What the pair by hand does not do by itself is keep the Field's count text equal to the input's count as the person types (TXF-67): the product writes it at each input.
  • On the web the two members stand in the text field's own shadow root, so the form meets the text field and not the input (TXF-63); focus passes through to the input (TXF-64).

生成自 specs/text-field.spec.json, tokens/sys/measure.tokens.json

组件负责的部分

  • 文本是在平台自己的文本元素里编辑的,所以输入法、听写、自动填充、选择和剪贴板都和在平台其他地方一样工作。日期或时间是明确的例外:那一排分段是文本字段自己实现的。
  • 日期或时间文本字段是一个由标签命名的组。每个分段是一个微调按钮(spin button),由它的单位和标签命名(“月,开始日期”),带有它的范围和值;空的分段读作 segmentEmptyText。
  • 必填的文本字段暴露为必填。标记本身对辅助技术隐藏,所以不会读两遍;选填的文字随文本字段的描述一起朗读。
  • 调整手柄是一个由 resizeLabel 命名的焦点停靠点,暴露为一个分隔条(separator),它的值是行数。方向键、Home 和 End 都能调整大小,所以没有任何操作依赖拖动;它的点击区域满足最小目标。
  • 标签就是无障碍名称,无论它是画出来的还是隐藏的。占位符绝不是。
  • 显示出来的辅助文本或错误文本,以及计数,是文本字段的描述。前缀和后缀在显示时也是,所以单位或货币会和值一起朗读。
  • 它暴露禁用状态,并在平台具备的地方暴露无效、必填和只读状态。日期或时间文本字段里的输入错误,暴露的方式与 error 完全相同。
  • 选择器按钮是一个由 pickerLabel 命名的 Button,暴露为会打开对话框,并且是最后一个分段之后的一个 Tab 停靠点。
  • 文本字段获得焦点期间出现或变化的错误,会以礼貌(polite)的方式朗读一次。计数在输入停顿一秒之后按显示的样子(“12/40”)朗读,绝不在每次按键时朗读。
  • 在平台具备搜索框角色的地方,搜索文本字段暴露为搜索框。
  • 代码字段和其他各种类型一样,是同一种文本框,设置 multiline 时为多行:没有任何角色、角色描述或文字说明它保存的是代码,它的值也没有任何部分被标记为拼写错误。Tab 始终会离开它。代码行不换行时,插入点会带着它滚动,而焦点显示在容器上,容器不滚动。
  • 焦点由文本字段自己的边线来显示,无论它来自键盘还是指针:stacked 文本字段是方框里面的一条线,filled 或 outlined 文本字段则是变粗并着色的边线。点击区域满足最小目标。
  • 按标签或方框会把焦点移到输入框上,但不操作任何东西。按帮助文本、错误文本或计数,什么都不会发生。
  • 名称、描述和状态的关联方式,与字段把它们关联到它框住的任何控件时完全一样。在下一层手写的字段,是同一个文本框。
  • 缺少标签会在调试构建中报告。

已知的平台差距:

  • SwiftUI、Compose、Flutter 和 React Native 保留 required 标志,而它不绘制任何标记;它们没有调整大小的模式和调整手柄,也没有分段。
  • Web:月或分填入第一位数字时,那一排分段会偏移几个像素;区域设置的最后一个分段是两位数字时,input 可能触发两次;在 Android 的屏幕键盘上,空分段上的 Backspace 无法退回上一个分段;比狭窄的文本字段更宽的一行会溢出,而不是滚动。
  • 产品无法通过把 value 设为它本来就有的空字符串,来清空只填了一半的日期行。
  • Web:在光晕(glow)焦点样式下,filled 文本字段的光晕在底部两角是圆的,而这两个角本身是直角。
  • Web:输入法的组合输入和自动填充后的标签只在 Chromium 中测试过。
  • Web:代码行不换行的只读代码字段,用键盘滚动的方式与浏览器滚动任何不可编辑的文本相同——在 Firefox 中用插入点,在 Chrome 中用方向键,在 Safari 中用 Option 加方向键。页面的方向改变时,Firefox 会让这样的文本字段回到它的左边缘。
  • SwiftUI、Compose、Flutter 和 React Native 还没有实现代码类型:在这些平台上,type 没有 code,文本字段也没有 wrap。
  • SwiftUI 没有无效、必填和只读状态。它们会被画出来或强制执行,但不会被朗读。错误只能通过描述里的文本听到。
  • SwiftUI 看不到输入法的组合输入,所以 maxLength 也会作用于仍在组合中的文本,你在组合期间设置的值也不会被暂缓。
  • SwiftUI:选中的文本以插入点的着色高亮,而不是文本字段的选区颜色。
  • SwiftUI 把只读文本字段的值画成可选择的文本,而不是文本字段。
  • SwiftUI 从 iOS 18 和 macOS 15 起才读取 .labelsHidden()。在更早的版本上,标签仍然会画出来。
  • React Native 看不到输入法的组合输入。你在组合期间更改的值,以及 maxLength 限制,都会立即应用,并可能结束这次组合。
  • React Native 没有无效、必填和只读状态。错误只能通过描述里的文本听到;必填和只读不会被朗读。
  • React Native:朗读、自动填充和各类型的键盘都只用模拟对象测试过,还没有在真机上测试。
  • iOS 上的 React Native 对插入点和选区高亮只有一种着色。文本字段保留插入点的颜色,所以高亮是这种颜色的一种淡色,而不是文本字段的选区颜色。
  • React Native:filled 文本字段的光晕只贴合它顶部的两个角;占位符和插入点的颜色是立即切换的,而不是按状态动效渐变。
  • Flutter 把搜索文本字段暴露为搜索输入类型,而不是搜索框角色。
  • 在 Android 和其他触摸屏上,Flutter 不绘制选区手柄和选区工具栏。长按和键盘快捷键仍然可以选择和复制;iOS 显示它的系统菜单。
  • Flutter:前置插槽里的可交互元素在焦点顺序中排在输入之前,而不是之后。
  • Compose 没有必填状态:文本字段改为把“Required”作为状态描述来朗读。
  • Compose:maxLength 和计数器按用户看到的字符计数,但表情符号序列和旗帜没有测试过。测试是在 JVM 上的 Robolectric 下运行的,它的字符规则不认识它们。
  • Compose:TalkBack 对错误和计数的朗读、真实的输入法以及真实的自动填充服务,都还没有在真机上测试过。

需要你做的事

  • 写一个说明这个值是什么的标签,即使把它隐藏起来,也要保留它。
  • 编写说明如何改正这个值的错误文本,并自己设置 error。在 SwiftUI 和 React Native 上,这段文本是用户听到错误的唯一途径。对于日期或时间文本字段,还要编写 invalidText 和 unavailableText,并给出 segmentEmptyText:没有它,空的分段朗读时不带任何值。
  • 用你的产品所用的语言给选择器按钮设置 pickerLabel,并在 pickerRequest 时打开选择器。
  • 设置 required,并给出 requiredText 和 optionalText:写在文本字段上,或者用 setWords({ requiredText, optionalText }) 为整个产品只给一次;resizeLabel 和 segmentEmptyText 也可以这样给。在 Web 上,Tanrei 不自带任何文字;缺少的文字会在开发时报告。参见产品的文字。在 SwiftUI、Compose、Flutter 和 React Native 跟上之前,它们的文本字段不绘制任何标记,SwiftUI 和 React Native 也不朗读必填状态:在这些平台上,请在标签里写明“必填”。
  • 用 resizeLabel 为调整手柄命名。没有它,调整手柄仍然会绘制,也能使用,但没有名称。
  • 把单位或货币放进 prefixText 或 suffixText,而不是放进值里,这样它会和值一起朗读,而不需要用户输入。
  • 对于代码字段,在 label 和 supportingText 里说明代码必须是什么(“Webhook 请求体”“JSON”),并在 errorText 里说明它错在哪里:文本字段并不认识这种语言。
  • 用你的产品所用的语言,给后置的图标按钮一个它自己的名称。
  • 在 Compose 上,“Invalid”和“Required”这两个词来自库的字符串资源(tanrei_invalid、tanrei_required)。在你的资源里覆盖它们,就可以翻译它们。
  • 在 Web 上,iOS Safari 会放大文本小于 16 px 的输入框。默认系统的文本字段在两种尺寸下都使用 16 px 的文本;把它改小的层会让这种放大重新出现。