跳到主要内容
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, Slider

概览

Web
tanrei-slider-field
schedule
SwiftUI
TanreiSliderField
radio_button_unchecked
Jetpack Compose
TanreiSliderField
radio_button_unchecked
Flutter
TanreiSliderField
radio_button_unchecked
React Native
SliderField
radio_button_unchecked

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

一个滑块字段、一个步进的,以及一个范围

滑块字段用于凭感觉选择、再用文字读出来的值:120 % 的文字大小、5 km 的距离、从 $100 到 $300 的价格。单独的滑块没有画出来的标签,它的轨道也不说明自己被设成了多少。滑块字段对应的是最常见的情况,可以直接使用:一个标签、用文字写出的值、轨道、一行帮助文本和一条错误。

组成部分是一个字段和一个滑块。字段是框架:标签、必填或选填标记、标签行末尾的值文本、帮助文本和错误。滑块是控件:轨道、手柄,以及用户用它们做的一切。滑块字段自己什么都不绘制,也没有令牌:它的每个属性都是这两者之一的属性,原样交给那一个成员,只有 valueHidden 例外,那是它自己的属性。

它的宽度与所在的位置相同,轨道填满这个宽度。

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

用法

value 是手柄的值,在范围中是末尾手柄的值;startValue 是范围的起始手柄的值。两个事件是滑块自己的事件:用户拖动期间每次变化时触发 input,变化结束时触发一次 change。监听到其中任何一个时,字段的 value 和 startValue 都已经是新的值。

<form>
  <tanrei-slider-field name="text-size" label="文字大小"
    supporting-text="适用于所有页面。"
    min="50" max="200" step="10" value="120"></tanrei-slider-field>

  <!-- 范围在同一个 name 之下先提交 start-value,再提交 value。请为每个手柄命名。 -->
  <tanrei-slider-field name="price" label="价格" range
    min="0" max="500" step="10" start-value="100" value="300"
    start-label="最低价格" end-label="最高价格"></tanrei-slider-field>
</form>

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

  // 表示值的文字,只给一次:它是属性(property),绝不是属性(attribute)。
  const percent = new Intl.NumberFormat(document.documentElement.lang, { style: 'percent' });
  const size = document.querySelector('[name="text-size"]');
  size.formatValue = (value) => percent.format(value / 100);
  size.addEventListener('input', () => applyTextSize(size.value));

  const dollars = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 });
  document.querySelector('[name="price"]').formatValue = (value) => dollars.format(value);
</script>
import { SliderField } from '@tanrei/react';

<SliderField name="text-size" label="文字大小" supportingText="适用于所有页面。"
  min={50} max={200} step={10} value={size}
  formatValue={(value) => percent.format(value / 100)}
  onInput={(event) => setSize(event.currentTarget.value)} />

用文字写出的值

值画在标签行的末尾,用的是滑块给手柄的那些文字——也就是屏幕阅读器读到的、数值指示器显示的同一些文字。它们按以下顺序取用:

文字 何时
valueText,范围的起始手柄则是 startValueText 文字由你自己来写,并且每次 input 时都要重写一遍。
formatValue 写出的内容 你只给一次一个把值变成文字的函数。每次变化时,滑块都为每个手柄调用它。
按页面的区域设置书写的数字 两者都没有给出。

范围的两个值用一个短破折号(en dash)连接,按阅读顺序排列:在从右到左的布局中,起始值的文字位于右边,也就是起始手柄所在的一边。滑块字段自己不写任何数字、单位或词。

与标签同在一行的值文本、自己另占一行的值文本,以及把它隐藏起来的字段

值文本位于标签第一行的基线上,标签在剩下的空间里换行。如果这样会让标签得不到这一行的一半,值文本就在标签之后另占一行,必要时在那里换行。valueHidden 把它去掉,用于数字对用户没有意义的滑块:音量、亮度。手柄仍然会把它的值告诉辅助技术。

下一层

用它的两个成员手写出来的同一个字段,画面是一样的,对辅助技术来说也是同一个滑块:

<tanrei-field label="文字大小" supporting-text="适用于所有页面。" value-text="120 %">
  <tanrei-slider name="text-size" min="50" max="200" step="10" value="120"></tanrei-slider>
</tanrei-field>

<script type="module">
  const field = document.querySelector('tanrei-field');
  const slider = field.querySelector('tanrei-slider');
  slider.formatValue = (value) => percent.format(value / 100);
  // 滑块字段自己会做的事:每次变化时,字段的值文本都取滑块的值文本。
  slider.addEventListener('input', () => (field.valueText = slider.shownValueText));
</script>

手写出来的同一个字段:一个字段,里面有一个滑块,值文本只给了一次——没有上面的监听器,它就不会保持同步

当你需要滑块字段没有提供的排布方式时,就往下走一层:

你需要 使用
标签、用文字写出的值、轨道、帮助文本和错误 滑块字段
滑块说不出来的值文本——一句话,“价格不限”——或者在框架里放你自己的东西 一个字段,里面放一个滑块,再加上你自己的 value-text
只要轨道,由绘制在别处的东西命名——表格的列、工具栏的字形、播放器的音量控件 单独使用的滑块,由它自己的 label 命名
纵向的滑块 单独使用的滑块:滑块字段没有方向设置
已经知道、要键入的值 数字字段,单独使用,或者放在滑块旁边

下到这一层之后,变的是各样东西写在哪里。name、value 和 startValue 写在滑块上,这时它就是表单面对的控件,它的事件也在它上面监听。label 和 disabled 仍然写在字段上,由字段转交给滑块。范围的末尾手柄由滑块自己的 label 命名,滑块字段把它叫作 endLabel。

规格

带产品文字的必填、选填、错误和禁用

各个成员提供什么

方面 来自字段 来自滑块
属性 label, labelHidden, supportingText, errorText, error, disabled, required, optional, requiredMark, requiredText, optionalText value, startValue, range, min, max, step, origin, stops, size, valueIndicator, valueText, startValueText, startLabel,以及 endLabel,也就是滑块的 label
事件 — input, change
插槽 label-icon —
令牌 comp.field.* comp.slider.*

有一个属性是滑块字段自己的:valueHidden。字段还有一个属性由滑块字段自己填写,不由你来设置:字段的 valueText,取自滑块的文字。

滑块有它自己的 disabled 和 orientation。两者都不是滑块字段的属性:字段的 disabled 禁用滑块,标签和值文本因此也随之变化;轨道则始终沿行内方向延伸:滑块字段没有垂直方向。

滑块总是有值,所以 required 在这里并不表示“不为空”。把每个控件都标为必填或选填的表单,也会给这一个加上标记;表单拒绝的是什么——音量为 0、范围太窄——要由你用 error 和 errorText 来说明。错误不会改变轨道的任何东西:传达它的是文字和无效状态。

手柄做什么,也就是拖动、按键、步进和数值指示器,写在滑块的页面上。框架做什么,也就是必填标记、值文本、帮助文本、错误以及错误怎样朗读,写在字段的页面上。

单独调整某一个字段

滑块字段没有自己的令牌。它的框架读取字段的令牌,它的轨道读取滑块的令牌,无论这些令牌设置在哪里。要改动某一个实例,就在它上面设置成员的令牌:

<tanrei-slider-field label="音量" value="40"
  style="--tanrei-comp-field-standard-value-text-color: var(--tanrei-sys-color-on-surface)"></tanrei-slider-field>

构成

构成

绘制在 Web 包所绘的测试样例 default 之上。
部件 说明
1 field The Field itself: the label row with its label glyph, its requirement mark and, at its end, the value text; and the supporting row with the help and the error text. Drawn by the Field from the Field's tokens. A member: it frames the component.
2 slider The Slider itself, as the Field's one control: its track, active track, handles, stop dots, state layer, value indicator and focus ring, drawn by the Slider from the Slider's tokens. A member: it holds the component's value.

属性

属性 类型 默认值 说明
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 Drawn above the track and, for one handle, its accessible name. While range is true it is the visible name of the pair, and each handle is named by startLabel and endLabel (SLF-5).
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 The label row takes the value text with it (FLD-29).
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 A slider has no error condition of its own to draw (FLD-8): the track and the handles stay as they are, and each handle is exposed as invalid.
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 one disabled of the slider field: the Field passes it to the Slider (FLD-14, SLD-20), whose own disabled is not a prop here, so that the label and the value text always follow.
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
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
value number 0 The value of the only handle, or of a range's end handle. — Slider
startValue number 0 The value of a range's start handle. Ignored while range is false. — Slider
range boolean false Draws two handles, choosing the range from startValue to value (a price filter). — Slider
min number 0 The lowest value, at the track's start. — Slider
max number 100 The highest value, at the track's end. — Slider
step number 0 0 is continuous. Above 0 the slider is discrete: its values are min plus whole multiples of step, up to max. — Slider
origin number null Where the active track starts. Null starts it at min. A value inside the range makes a centered slider (a balance control) whose active track grows toward either side. Ignored while range is true. — Slider
stops boolean false Draws a stop dot at every value a discrete slider can take. Meant for a few steps; ignored while step is 0. — Slider
size small | medium | large "small" The track's weight and the stop dots' size (and, where a system says so, the handle's height). Small is the everyday slider; medium and large give a prominent control (a volume or brightness slider) more weight. The hit area always meets the minimum target. A design system may add values. — Slider Changes nothing of the frame.
valueIndicator none | active | always "none" Whether a handle shows its value beside it: never, while it is pressed or has keyboard focus, or all the time. — Slider
valueText string "" The value as words ("40 %", "3 stars"), spoken and shown by the value indicator in place of the number. For a range, the end handle's. — Slider Also drawn at the end of the label row (SLF-3).
startValueText string "" The start handle's valueText, for a range. — Slider
startLabel string "" Names a range's start handle ("Minimum price"). Required while range is true. — Slider
endLabel string "" Names the slider (a range's end handle) for assistive technology. Never drawn: a visible label is the product's text next to the slider, in the same words. Required. — Slider The Slider's label under the name of the handle it names, because label is the visible name here. Read only while range is true, and required then (SLF-5).
valueHidden boolean false Does not draw the value at the end of the label row. For a slider set by feel, whose number means nothing to the person (a volume, a brightness). The handle still says its value to assistive technology, and valueIndicator still shows it.

插槽

插槽 说明
label-icon A glyph before the label. — Field

事件

事件 说明
input The person changed a value; carries value and, for a range, startValue. Emitted for every change during a drag, not when the product sets a value. — Slider
change The person finished changing a value (the press ended, or a key or assistive-technology adjustment was made); carries value and, for a range, startValue. Always after the input it follows. — Slider

状态

行为

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

ID 需求
SLF-1 THE slider field SHALL be a Field whose one control is a Slider — the two components themselves, each drawn from its own tokens in every state and behaving by its own sentences (FLD-1 to FLD-24, FLD-29 and FLD-30; SLD-1 to SLD-26) — and SHALL draw nothing of its own, have no token of its own, and give neither member a look, a state or a size that the member does not have where it stands alone.
SLF-2 THE slider field SHALL give each member the value of every prop that is that member's, at once and whenever it changes, and SHALL hold none back: the Field takes label, labelHidden, supportingText, errorText, error, disabled, required, optional, requiredMark, requiredText, optionalText and the glyph of the label-icon slot; the Slider takes value, startValue, range, min, max, step, origin, stops, size, valueIndicator, valueText, startValueText, startLabel and, as its own label, endLabel. THE slider field SHALL give the Slider no orientation, so that the track always runs along the line (SLD-21), and SHALL NOT set the Slider's own disabled: the Field disables its control (FLD-14).
SLF-3 WHILE valueHidden is false THE slider field SHALL give the Field, as its value text (FLD-29), the words the Slider gives its handle as that handle's value (SLD-18): WHILE range is false the only handle's; WHILE range is true the start handle's, an en dash with a space on each side, and the end handle's, each of the two an isolated run and the two in reading order, so that in a right-to-left layout the start's words stand at the right of the dash as the start handle stands at the right of the track. THE slider field SHALL NOT write a number, a unit or a word itself. WHILE valueHidden is true THE slider field SHALL give the Field no value text.
SLF-4 WHEN a value changes — at every input of a drag, by a key, by assistive technology, because the product set value or startValue, or because min, max or step changed what the Slider draws (SLD-22) — or valueText, startValueText or the product's function for a value changes, THE slider field SHALL show the new value text in the same frame in which the handle is drawn at its new place, without a motion and without an announcement of its own: the handle says its value to assistive technology itself (SLD-19), and the Field keeps the drawn text from it (FLD-30).
SLF-5 WHILE range is false THE slider field SHALL have its handle named by label, as the Field names its control (FLD-10). WHILE range is true THE slider field SHALL have the start handle named by startLabel and the end handle by endLabel (SLD-19), which should each hold the words of label so that what is seen is in what is heard, and WHILE either is empty SHALL have that handle named by label and in debug builds report that a handle of a range has no name of its own. THE slider field SHALL have every handle described by the text the supporting row shows, with optionalText while optional is true (FLD-11), and exposed as invalid while error is true (FLD-8); WHILE required is true it SHALL have every handle exposed as required where the platform's slider has such a state and, where it has none (the web's slider role), SHALL have requiredText added to the handle's description instead.
SLF-6 THE slider field SHALL hold value and startValue as its own and report them as the Slider does: WHEN the Slider emits input or change THE slider field SHALL emit the same event exactly once, carrying value and, for a range, startValue (SLD-2 to SLD-4), with its own value and startValue already equal to what the event carries; WHEN the product sets value or startValue THE slider field SHALL emit nothing (SLD-16). WHERE the platform has forms THE slider field SHALL be the one control the form sees — it carries the name, submits what the Slider submits, is reset and restored as the Slider is, and submits nothing WHILE disabled is true — and neither member SHALL be a second one.
SLF-7 THE slider field SHALL add no stop to the focus order and handle no key: its only focus stops are the Slider's handles, in the Slider's order (SLD-5), and every key is the Slider's (SLD-3). WHEN focus is given to the slider field itself, or the person presses the label row (FLD-12), THE slider field SHALL move focus to the only handle and, WHILE range is true, to the start handle, and SHALL change no value.
SLF-8 WHILE disabled is true THE slider field SHALL be a disabled Field holding a disabled Slider (FLD-14, SLD-20): no handle takes focus or changes, no event is emitted, the value indicator is not shown, and the label, the requirement mark, the supporting row and the value text are drawn from the Field's disabled tokens; THE slider field SHALL keep the value text drawn, because what a disabled control is set to is still read.
SLF-9 WHILE the text size is enlarged THE slider field SHALL grow as the Field grows (FLD-16) and SHALL leave the Slider's track, handles and hit area as they are (SLD-7); at 200% in a width of 320 no text SHALL be cut, lie over another part or need scrolling in two directions: the label wraps beside the value text, and a value text too wide to share the label's line moves whole to a line of its own (FLD-29).
SLF-10 THE slider field SHALL take its layout direction from where it is used and have no direction setting of its own, and its two members SHALL take the same one (FLD-19, SLD-25). WHERE the layout direction is right-to-left the frame and the track mirror together — the label row starts at the right with the value text at its left end (FLD-18), the track runs from the right (min) to the left (max) and the arrow keys follow it (SLD-21) — and the words of a value SHALL NOT be mirrored: each is laid out as text of its own direction (FLD-21, SLD-26), so that a number keeps its digits, its sign and its unit in their own order.
SLF-11 WHERE the platform draws with a person's own forced colors THE slider field SHALL add nothing and remove nothing: the Field draws its texts as FLD-17 says and the Slider its track and handles as it does standing alone, and the error SHALL still be carried by the words of errorText and by the invalid state of each handle (FLD-8), never by a color.
SLF-12 THE slider field SHALL be, in what is drawn and in what assistive technology is told, exactly what a Field holding a Slider is when a product writes the two by hand with the same props and gives the Field the same value text, which is the one prop of a member the slider field fills itself: every fixture of the slider field and its twin among the Field's fixtures are the same picture, with the same names, descriptions, values and states, and a difference is a defect of the slider field.
SLF-13 THE slider field SHALL be a block as wide as its place in the layout, with no width of its own, and its Slider SHALL fill it from the start edge to the end edge, as a Slider standing in a Field does (FLD-1); the label row and the supporting row are as wide as the track's box.
SLF-14 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 slider 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 slider field is invalid — except those the slider field owns (aria-label, aria-labelledby, aria-disabled), which it SHALL move and not expose: its name is label (SLF-5). 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 slider field SHALL take it off that element. WHEN the product sets an aria- attribute the slider field owns THE slider field SHALL report it in debug builds.

各平台名称对照

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

规格 Web SwiftUI Jetpack Compose Flutter React Native
组件 tanrei-slider-field TanreiSliderField TanreiSliderField TanreiSliderField SliderField
label · 属性 label label label labelText label
labelHidden · 属性 labelHidden labelsHidden labelHidden labelHidden labelHidden
supportingText · 属性 supportingText supportingText supportingText helperText supportingText
errorText · 属性 errorText errorText errorText errorText errorText
error · 属性 error error isError isError error
disabled · 属性 disabled disabled enabled enabled disabled
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
value · 属性 value value value value value
startValue · 属性 startValue value value start startValue
range · 属性 range value value range range
min · 属性 min bounds valueRange min minimumValue
max · 属性 max bounds valueRange max maximumValue
step · 属性 step step step step step
origin · 属性 origin origin origin origin origin
stops · 属性 stops showsStops showStops showStops stops
size · 属性 size size size size size
valueIndicator · 属性 valueIndicator valueIndicator valueIndicator valueIndicator valueIndicator
valueText · 属性 valueText valueText stateDescription semanticValue valueText
startValueText · 属性 startValueText startValueText startStateDescription startSemanticValue startValueText
startLabel · 属性 startLabel startLabel startContentDescription startSemanticLabel startAccessibilityLabel
endLabel · 属性 endLabel endLabel endContentDescription endSemanticLabel endAccessibilityLabel
valueHidden · 属性 valueHidden valueHidden valueHidden valueHidden valueHidden
input · 事件 input value onValueChange onChanged onValueChange
change · 事件 change onCommit onValueChangeFinished onChangeEnd onSlidingComplete
备注 Class TanreiSliderField; React SliderField. A custom element with a shadow root that holds its members' own elements, composed as every field element is: <tanrei-field> in the shadow root, and in its default slot one <tanrei-slider> with no label of its own while range is false; the host's label-icon slot is passed on to the Field's. The host is display: block and as wide as its place (SLF-13). What differs here: (1) static formAssociated = true on the host, which carries name, submits what the Slider submits (the value; for a range the start value and then the value under the same name), and resets and restores it — the inner Slider has no name and is outside the form; (2) the shadow root does not delegate focus, because a press on the help or the error text must do nothing (FLD-12): focus() is overridden to go to the first handle, and the Slider's own shadow root delegates as before; (3) input and change leave the shadow root once each with the host as their target, after the host's value and startValue are set; (4) exportparts gives the Slider's documented parts under their own names and the Field's under theirs, with the two members' hosts as the parts field and slider, and the host has no other part of its own; (5) the host writes the Field's value-text from the Slider's value text at every input, in the same task, and joins a range's two in two <bdi> runs with an en dash between; (6) formatValue is a property of the host, never an attribute, ((value: number) => string) | null, null by default, handed to the Slider (field.formatValue = (value) => percent.format(value / 100)). Attributes are kebab-case and are the members': label-hidden, supporting-text, error-text, required-mark, required-text, optional-text, start-value, value-indicator, value-text, start-value-text, start-label, and end-label, value-hidden. No custom property of a member is written by the host: a token set on the host for one instance (--tanrei-comp-field-…, --tanrei-comp-slider-…) reaches the member by inheritance. Words: required-text and optional-text left empty read setWords(), as the Field's do. One level down, the same picture and the same tree: <tanrei-field label="Volume" value-text="40"><tanrei-slider name="volume" value="40"></tanrei-slider></tanrei-field>, with the product writing value-text at each input. With scripting off nothing of it is drawn and nothing is submitted, as for every control with a shadow root. Planned. Not built on this platform yet, nor is this platform's Field: TanreiSliderField(_ label:, value: Binding<Double>, in bounds: ClosedRange<Double> = 0...100, step:, supportingText:, errorText:, error:, required:, optional:, format: ((Double) -> String)? = nil), a view whose body is TanreiField(label, valueText: …) { TanreiSlider(value: $value, in: bounds, …) }; a range is the initializer whose value is a Binding<ClosedRange<Double>> and which takes startLabel and endLabel. labelHidden follows .labelsHidden() and disabled follows .disabled(_:), as the Field's do. One field set apart: .tanreiTheme(_:) on the view. Planned. Not built on this platform yet, nor is this platform's Field: TanreiSliderField(label = "Volume", value = volume, onValueChange = { volume = it }, valueRange = 0f..100f, supportingText = …, isError = …, format: ((Float) -> String)? = null), a composable that calls TanreiField(label, valueText = …) { TanreiSlider(…) }; a range is the overload whose value is a ClosedFloatingPointRange<Float>. enabled is the inverse flag. The Slider's names are kept for what is its own (stateDescription), though the slider field also draws that text. One field set apart: TanreiTheme(…) { } around the call. Planned. Not built on this platform yet, nor is this platform's Field: TanreiSliderField(labelText: 'Volume', value: volume, onChanged: (v) => …, helperText: …, errorText: …, isError: …, format: (double value) => …), a widget that builds TanreiField(labelText:, valueText:, child: TanreiSlider(…)); a range is the named constructor TanreiSliderField.range(start:, end:, onChanged: (start, end) {…}). enabled is the inverse flag; a null onChanged also disables it, as the Slider's does. One field set apart: a TanreiTheme widget around it. Planned. Not built on this platform yet, nor is this platform's Field: <SliderField label="Volume" value={volume} onValueChange={setVolume} supportingText="…" formatValue={(value) => …} />, a component that renders <Field label valueText={…}><Slider … /></Field>; a range adds range and startValue, and both callbacks receive (value, startValue). One field set apart: a provider around it.

令牌

令牌 引用 解析值(浅色)
--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/slider-field.spec.json, tokens/comp/field.tokens.json, design/tokens/

Web 上绘制方式的不同之处

  • 这个元素的 shadow root 里有一个 <tanrei-field>,其中放着一个 <tanrei-slider>,此外什么都没有。导入这个元素,两个成员也随之完成定义。
  • 这个元素是与表单关联的元素。它接受 name 和 form;提交的是值,范围则在同一个 name 之下先提交起始值、再提交值,禁用时什么都不提交;重置为 value 和 start-value 属性(attribute)的值;跟随被禁用的 <fieldset>。它的两个成员不向表单添加任何条目。
  • input 和 change 是滑块的 CustomEvent,带有 detail.value(范围还带有 detail.startValue)。它们的 target 是这个元素,在每个监听器里,event.target.value 都是新的值。
  • formatValue 是属性(property),绝不是属性(attribute):(value: number) => string,或者 null。
  • 范围的两段值文本写进字段的 value-text 这同一段文本里,中间是一个短破折号。每一段都是一个有自己方向的片段——包在首强隔离符(first-strong isolate,U+2068 和 U+2069)里——除非它是“$100”或“3,400,000”这样的纯数字:这种文本在两种方向下本来就是一个从左到右的片段,所以原样写入。
  • 部件:field 和 slider 是两个成员。成员自己的部件在这里的名称与下一层相同:label、label-icon、requirement-label、value-text、supporting-text;track、active-track、stop-indicator、container、handle、value-indicator。
  • 这个元素本身不能获得焦点:focus() 会把焦点交给手柄,范围则交给起始手柄;页面自己的 <label for> 也会把焦点移到那里。
  • 这个元素没有角色。用 label 为它命名,标签绘制在别处时再加上 label-hidden;绝不要用 aria-label,它在这里不为任何东西命名。
  • 属性(attribute)就是成员自己的那些:label-hidden、supporting-text、error-text、required-mark、required-text、optional-text、start-value、value-indicator、value-text、start-value-text、start-label;另外还有 end-label 和 value-hidden。
  • 在元素完成定义之前,它没有尺寸,只显示作为子元素的 label-icon。必须在没有脚本时也能工作的页面,用一个 <label> 包住一个普通的 <input type="range">。

指南

推荐

  • 单独一个数字不够清楚时,用 formatValue 给出表示值的文字:百分比、货币、单位。
  • 用 startLabel 和 endLabel 为范围的两个手柄命名,措辞里要包含 label 的词:“价格”之下是“最低价格”和“最高价格”。
  • 标签没有说明这个值改变什么的地方,在 supportingText 里说明。
  • 用文字说明拒绝的是什么,使用 error 和 errorText。
  • 凭感觉设置、数字对用户毫无意义的值,使用 valueHidden。

避免

  • 不要在标记里只写一次 valueText,却让用户可以移动滑块:第一次拖动之后,这些文字就不对了。请给出 formatValue,或者每次 input 时都重写 valueText。
  • 不要把滑块字段放进字段。它本身已经是字段;调试构建会报告这一点。
  • 不要用 aria-label 为它命名。不该绘制标签的地方,给出 label 和 labelHidden。
  • 不要用 valueIndicator="always" 来显示值:这是值文本的事,而指示器会盖在标签行上。
  • 不要把滑块字段用于必须精确的值。请使用数字字段,单独使用,或者放在它旁边。

无障碍

角色
slider
标注
The slider field has no role of its own: assistive technology meets the Slider's handles, each a slider (SLD-19), What the slider field ties together (SLF-5): label is the accessible name of the only handle; a range's two handles are named by startLabel and endLabel; the supporting text with, in error, the error text after it is every handle's description, with optionalText while optional is true; every handle is invalid while error is true and required, or described by requiredText where the role has no required state, while required is true. Each handle's value is said in the words of SLD-18 — valueText, or what the product's function writes, or the platform's number — and the same words are drawn at the end of the label row, where they are kept from assistive technology so that a value is not read twice (FLD-30).
最小目标
即使外观尺寸更小,点击区域也满足最小目标尺寸。 minimum 32px · pointer 44px · spacing 8px · touch 48px

键盘操作

按键 动作
Tab / Shift+Tab Moves focus to the handle — for a range to the start handle, then the end handle — and on from it: the stops are the Slider's, and the slider field adds none (SLF-7).
Every other key The Slider's own, as its spec's keyboard table says (SLD-3). The slider field handles no key.

备注

  • Everything else is a member's and is said in its spec: the Field's rows, requirement marks, error announcement and press on the label (FLD-2 to FLD-12); the Slider's roles, value limits, target size and contrast (SLD-7, SLD-19 and its accessibility notes).
  • A value a person cannot read is a value only some people are told. The handle says its value to assistive technology, and a track does not say it to the eye; the value text at the end of the label row is drawn unless valueHidden says the number means nothing to the person.
  • A number with a unit is words, and the words are the product's: valueText for a value the product writes itself, or one function from a value to its words, given once, which the Slider asks at every change (SLD-18). The slider field writes none, and joins a range's two with an en dash, which is a mark.
  • A slider always has a value, so required does not mean "not empty" here: a product that marks every control of a form as required or optional marks this one too, and says with error and errorText what it refuses (a level of 0, a range too narrow).
  • Dragging is never the only way (WCAG 2.5.7): a press on the track moves the handle to it, and the keys step it (SLD-2, SLD-3). A value that must be entered exactly is a number field, alone or beside a slider.
  • 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.

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

组件负责的部分

  • 每个手柄都是一个滑块。只有一个手柄时,它以 label 命名,无论标签是否绘制;范围的两个手柄以 startLabel 和 endLabel 命名,缺少其中一个时以 label 命名,调试构建会报告这一点。
  • 每个手柄的描述是显示出来的辅助文本或错误文本,字段为选填时还加上选填文字;error 为 true 时,它暴露为无效,disabled 为 true 时,暴露为禁用。Web 的滑块角色没有必填状态,所以必填的滑块字段把 requiredText 放在描述里。
  • 每个手柄都用文字说出自己的值。同样的文字画在标签行的末尾,而那里的文字不提供给辅助技术,所以一个值只读一次。
  • 画出来的标签、必填和选填标记以及值文本,不会再暴露第二次;值文本的变化也不朗读:手柄会说出自己的值。
  • 它的焦点停靠点就是滑块的手柄——一个,或者范围的两个——每个按键也都是滑块的。按下标签行会把焦点移到手柄上,不改变任何东西;按下帮助文本什么都不会发生。
  • 禁用时,值文本仍然画出来:禁用的控件被设成了什么,仍然读得到。
  • 方向、放大的文本和强制颜色模式,由各个成员各自应对:在从右到左的布局中,框架和轨道一起镜像,而表示值的文字保持自己的顺序;文本放大到 200% 时,没有任何内容被截断,也没有任何内容横向滚动。
  • 它没有自己的文字。requiredText 和 optionalText 留空时,由字段从产品的文字里读取。

Web 上的已知差距:在元素完成定义之前,什么都不绘制;范围的两个手柄没有作为一个以 label 命名的组暴露出来。

需要你做的事

  • 始终给出 label,范围还要给出 startLabel 和 endLabel。
  • 单独一个数字让人看不懂时,给出表示值的文字,并在值变化时让它们始终正确。
  • 用 errorText 说明拒绝的是什么。颜色绝不是唯一的提示。
  • 用户必须输入精确值的地方,在滑块旁边提供一个数字字段:拖动绝不是移动手柄的唯一方式,但它终究不是键入数值的方式。