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

纸片(Chip)

排成一行的带标签的小控件:用于建议的操作或通往某处的链接、可开可关的筛选条件,或者用户输入后可以移除的项。

content_copy以 Markdown 格式查看
类别
选择
令牌
comp.chip
层级
基础
用于
Chip field

概览

Web
tanrei-chip
schedule
SwiftUI
TanreiChip
schedule
Jetpack Compose
TanreiChip
schedule
Flutter
TanreiChip
schedule
React Native
Chip
schedule

生成自 docs/status/status.json, specs/chip.spec.json

种类

event

用法

Web 和 React 的示例展示的是当前的规格。SwiftUI、Compose、Flutter 和 React Native 待跟进:它们提供的仍是纸片(Chip)的上一版——圆角较小,图标较小,移除操作用 close 图标,没有 destructiveRemove,也没有链接形式(href);在跟上之前,它们的示例展示的也是那一版。

<tanrei-chip label="添加到日历"><span slot="icon">event</span></tanrei-chip>

<!-- 带 href 的操作纸片是链接。在新标签页中打开的链接会显示一个标记,并用你提供的文字说明这一点。 -->
<tanrei-chip label="营业时间" href="/visit/hours/"></tanrei-chip>
<tanrei-chip label="路线" href="https://example.com/map" target="new" target-label="在新标签页中打开"></tanrei-chip>

<!-- 筛选纸片会切换自己的 selected,并触发 change。 -->
<tanrei-chip kind="filter" label="素食"></tanrei-chip>

<!-- 输入纸片有第二个按钮,它的名称由 remove-label 给出。 -->
<tanrei-chip kind="input" label="李华" remove-label="移除李华">
  <img slot="avatar" src="ada.jpg" alt="" />
</tanrei-chip>

<!-- 移除附件会丢失这个附件:给移除操作加上破坏性外观。 -->
<tanrei-chip kind="input" label="report.pdf" remove-label="移除 report.pdf" destructive-remove></tanrei-chip>

<script type="module">
  import '@tanrei/web/components/chip';
  document.querySelector('[kind="filter"]').addEventListener('change', (e) => filter(e.detail.selected));
  // 纸片会留在 DOM 中:由页面移除它,并把焦点移到相邻的纸片上。
  document.querySelector('[kind="input"]').addEventListener('remove', (e) => removeRecipient(e.target));
</script>
import { Chip } from '@tanrei/react';

<Chip label="添加到日历" onClick={addToCalendar}><span slot="icon">event</span></Chip>
<Chip label="营业时间" href="/visit/hours/" />
<Chip label="路线" href="https://example.com/map" target="new" targetLabel="在新标签页中打开" />
<Chip kind="filter" selected={vegetarian} label="素食" onChange={(e) => setVegetarian(e.detail.selected)} />
<Chip kind="input" label="李华" removeLabel="移除李华" onRemove={() => removeRecipient(ada)} />
<Chip kind="input" label="report.pdf" removeLabel="移除 report.pdf" destructiveRemove onRemove={() => removeAttachment(report)} />
import Tanrei

// 种类由所用的构造器决定。
TanreiChip("添加到日历", icon: "event", action: addToCalendar)
TanreiChip("素食", isSelected: $vegetarian)
TanreiChip("李华", removeLabel: "移除李华", action: openContact, onRemove: removeAda)
import io.github.chnotchy.tanrei.components.TanreiChip
import io.github.chnotchy.tanrei.primitives.TanreiText

// 种类由所用的重载决定。只有 onClick 的是操作纸片。
TanreiChip(onClick = ::addToCalendar, label = "添加到日历", icon = { TanreiText("event") })

// selected 加上 onSelectedChange 是筛选纸片。
TanreiChip(selected = vegetarian, onSelectedChange = { vegetarian = it }, label = "素食")

// onClick 加上 onRemove 是输入纸片。由你把它从列表中移除,并自行移动焦点。
TanreiChip(
    onClick = { openContact(ada) },
    onRemove = { recipients -= ada },
    label = "李华",
    removeContentDescription = "移除李华",
    avatar = { ContactPhoto(ada) },
)
import 'package:tanrei/tanrei.dart';

// 种类由所用的构造函数决定。
TanreiChip(label: '添加到日历', icon: const Icon(AppSymbols.event), onPressed: addToCalendar);

TanreiChip.filter(
  label: '素食',
  selected: vegetarian,
  onSelected: (bool value) => setState(() => vegetarian = value),
);

TanreiChip.input(
  label: '李华',
  removeSemanticLabel: '移除李华',
  avatar: Image.asset('ada.png', fit: BoxFit.cover),
  onDeleted: () => setState(() => recipients.remove(ada)), // 并自行移动焦点
);
import { Chip } from '@tanrei/react-native';

<Chip label="添加到日历" icon="event" onPress={addToCalendar} />
<Chip kind="filter" label="素食" selected={vegetarian} onSelectedChange={setVegetarian} />
<Chip
  kind="input"
  label="李华"
  avatar={{ uri: ada.photo }}
  removeAccessibilityLabel="移除李华"
  onRemove={() => removeRecipient(ada)}
/>

规格

变体、尺寸和禁用

eventeventevent

移除操作:普通(默认)和破坏性;悬停或聚焦即可看出差别

链接纸片:在当前页打开、在新标签页中打开、禁用

在默认系统中,每个纸片都是胶囊形,文本放大使它变高时也仍然是胶囊形。它的图标大到高度所能容纳的程度:中号纸片上是 24,小号纸片上是 20。选中的筛选纸片上的对勾是系统图标集里的 check 图标,移除图标是 cancel,始终以填充样式绘制。纸片不绘制自己的标记,所以想要其他标记的设计系统,应当选择另一套图标集。outlined 纸片的边线是细线,也就是分隔线所画的那种不显眼的线。

构成

构成

绘制在 Web 包所绘的测试样例 action-outlined 之上。
部件 说明
1 container The shape that holds the chip's content and carries hover, focus and press of the chip itself.
2 outline · 可选 The border, drawn inside the container's edge. Transparent where the look has none (elevated, selected).
3 icon · 可选 Leading glyph of the system's icon set. A selected filter chip shows the glyph `check` in its place.
4 avatar · 可选 Leading image of an input chip (a person, a file), in place of the icon.
5 label Text that names the choice, action or item.
6 remove · 可选 An input chip's trailing remove action: the glyph `cancel` of the system's icon set, drawn filled, on a round container that carries its own hover, focus and press and sits concentric with the chip's round end: as far from the end as from the top and the bottom. A separate button for assistive technology. It takes the error color while it is hovered, focused or pressed only while destructiveRemove is true.
7 focus-ring Gap-then-ring indicator shown for keyboard focus, around the chip or around the remove action, whichever has focus.

尺寸

尺寸

长度 令牌 值(px)
container padding-inline-with-icon tanrei.comp.chip.container.padding-inline-with-icon.medium 8
container gap tanrei.comp.chip.container.gap.medium 8
container height tanrei.comp.chip.container.height.medium 32
icon size tanrei.comp.chip.icon.size.medium 24
绘制在 Web 包所绘的测试样例 action-outlined 之上。

属性

属性 类型 默认值 说明
kind action | filter | input "action" What the chip does. action: a button that performs or suggests something, or with href a link to it. filter: a two-state control, on or off. input: an item the user entered, which the user can remove. Kind decides the role and the parts, not the look.
variant outlined | elevated "outlined" Look. Outlined sits flat on a plain surface; elevated is raised for chips over media or a busy background. A design system may add values.
size small | medium "medium" Visual height (24 / 32). Small is for dense places, such as entered items inside a field or tags in a table row. The hit area always meets the minimum target. A design system may add values.
selected boolean false Whether a filter chip is on. Ignored unless kind is filter.
value string "" What a component that holds the selection of a set of filter chips names this chip by: the Tag group's value and values (TGR-17). Unique within the set. Not drawn and not exposed; the chip itself does nothing with it.
disabled boolean false Prevents interaction and removes the chip, and its remove action, from the focus order.
href string "" The destination of an action chip: an address as the platform's links take one (a URL; on the web a path or a fragment as well). Not empty, it makes the chip a link and not a button: the link role, activated by pointer, touch or Enter and not by Space, opened by the platform as its own links are, and drawn exactly as the action chip it would otherwise be (CHP-33, CHP-34). Ignored unless kind is action: a filter chip holds a state and an input chip a second action (CHP-37).
target same | new "same" Where the destination opens. same: where the person is — the same tab on the web, the app's own handling of the address elsewhere. new: a new context — a new tab or window on the web, outside the current screen elsewhere — which the chip marks with the glyph open_in_new after its label and says through targetLabel (CHP-35). Ignored without href.
targetLabel string "" What assistive technology reads after the link's name while target is new ("opens in a new tab"). Tanrei ships no words of its own, so the product gives them in its own language, as it gives a chip its removeLabel. Required while target is new; ignored otherwise.
label string "" The visible text and the accessible name.
removeLabel string "" The accessible name of an input chip's remove action. It should name the item ("Remove Ada Lovelace"), because the remove action is a separate element. Required when kind is input; ignored otherwise.
destructiveRemove boolean false Whether an input chip's remove action reads as destructive: at rest it looks as it does without the flag; while it is hovered, keyboard-focused or pressed its glyph and state layer take the error color. Set it where removing the item loses something that cannot be put back in a step (an attachment, a recipient already invited); leave it off where the person can put the item back in one step (an applied filter, a search term). It changes color only: the remove action's name, glyph, size and behaviour are the same either way. Ignored unless kind is input.
removable boolean true Whether an input chip draws its remove action. False for an entry that cannot be taken away now (a read-only field's): no remove action is drawn, so none stands there doing nothing. Ignored unless kind is input.

插槽

插槽 说明
icon Leading icon (a glyph of the system's icon set).
avatar Leading image of an input chip. Ignored for other kinds.

事件

事件 说明
click The chip itself was activated by pointer, touch, keyboard or assistive technology. Not emitted by the remove action. A link chip emits it before its destination opens; a product that takes the navigation itself (a client-side router) cancels it (CHP-34).
change A filter chip was activated; carries the new value of selected.
remove The user asked to remove an input chip, with its remove action or with Backspace or Delete. The chip does not remove itself.

状态

行为

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

ID 需求
CHP-1 WHEN the user activates the chip by pointer, touch, Enter or Space — a link chip (CHP-33) by pointer, touch or Enter — THE chip SHALL emit click exactly once.
CHP-2 WHILE disabled is true THE chip SHALL NOT emit click, change or remove, SHALL NOT be focusable, nor its remove action, and SHALL expose the disabled state to assistive technology.
CHP-3 WHILE hovered, keyboard-focused or pressed THE chip SHALL render its container, outline, label, icon, elevation and opacity from the component tokens of the highest-precedence active state, with precedence pressed over focused over hovered; focus gained by pointer or touch SHALL NOT select the focused state.
CHP-4 WHEN the chip receives keyboard focus THE chip SHALL show the focus ring without animation; focus gained by pointer or touch SHALL NOT show it.
CHP-5 WHERE the visual size is below the minimum target THE chip SHALL extend its hit area on both axes to sys.target.touch for touch input and sys.target.pointer for fine pointers, without changing its visual size; a platform that cannot tell the input type SHALL use sys.target.touch.
CHP-6 WHILE disabled is true THE chip SHALL render its container, outline, label, icon, remove action, elevation and opacity from the disabled component tokens of its variant and, for a filter chip, of its selection, and SHALL draw an avatar at sys.state.disabled-content-opacity.
CHP-7 WHEN variant or size names a value that the active design system does not have, or kind names a value this spec does not have, THE chip SHALL render and behave with the default value (kind action, and the default variant's or size's tokens), and in debug builds report the unknown value.
CHP-8 WHEN the chip is pressed by pointer, touch, Enter or Space THE chip SHALL show the pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner.
CHP-9 WHILE pressed THE chip SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole container at once; ripple grows the pressed state layer from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole control and draws no state layer; none draws no answer to the press. Under ripple and dim only the container color stays with the state under the press; outline, label, icon, elevation, opacity and shape still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.chip.motion.feedback-duration with comp.chip.motion.state-easing, fades over the state duration of its size (comp.chip.motion.state-duration.small or .medium) with comp.chip.motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced.
CHP-10 THE chip SHALL ease its container corners by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0) and show its keyboard focus as sys.behavior.focus-style names (ring or glow); a platform that cannot draw a choice SHALL fall back to the first value and state that in its README.
CHP-11 WHEN kind is filter and the user activates the chip THE chip SHALL emit click and then change with the opposite of selected, in that order (as a native checkbox does), and SHALL expose selected to assistive technology as pressed or not pressed; WHILE kind is not filter THE chip SHALL ignore selected and SHALL NOT expose a pressed state.
CHP-12 WHILE kind is filter and selected is true THE chip SHALL render its container, outline, label and icon from the selected parts of its variant (comp.chip.outlined.selected.container.color for outlined at rest, and so on); otherwise it SHALL render them from the variant's own parts (comp.chip.outlined.container.color, and so on).
CHP-13 WHILE kind is filter and selected is true THE chip SHALL show the glyph check of the system's icon set in the leading position, in place of its icon when it has one, and SHALL draw no mark of its own; WHEN selected changes THE chip SHALL show or hide the check, change its width with it, and change to or from the selected colors over comp.chip.motion.selection-duration with comp.chip.motion.selection-easing, and SHALL show the new condition at once where motion is reduced.
CHP-14 THE chip SHALL draw its icon, its check and its remove glyph at the icon size of its size (comp.chip.icon.size.medium for medium, and so on) and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max, the icon and the remove glyph with sys.icon.weight and the check with comp.chip.check.weight (not sys.icon.weight: the check is a control's own mark, drawn on the selected fill to tell on from off, as the checkbox's is, CHK-12), the weight changing the stroke of the glyph only and never its box; the icon and the check SHALL take the family and fill of its selection (sys.icon.family.selected and sys.icon.fill.selected only while kind is filter and selected is true; otherwise sys.icon.family.unselected and sys.icon.fill.unselected) and the icon color of its look (comp.chip.outlined.icon.color, comp.chip.outlined.selected.icon.color, and so on); the remove glyph SHALL always be drawn filled, with sys.icon.family.selected and sys.icon.fill.selected, in the glyph color of the parts CHP-20 and CHP-27 name (comp.chip.outlined.destructive-remove.icon.color or comp.chip.outlined.remove.icon.color, and so on).
CHP-15 WHERE kind is input and an avatar is given THE chip SHALL draw it in the leading position, in place of an icon, as an image of the avatar size of its size (comp.chip.avatar.size.medium for medium, and so on) clipped to comp.chip.avatar.shape and hidden from assistive technology; WHERE kind is not input THE chip SHALL ignore an avatar and report it in debug builds.
CHP-16 THE chip SHALL draw a container at least the height of its size (comp.chip.container.height.medium for medium, and so on) with corners comp.chip.container.shape, never larger than half the container's height (a pill where the value is sys.shape.corner.full, at any height the chip grows to), and an outline comp.chip.outline.width wide inside its edge, and lay out in one row its leading icon, check or avatar, its label and its remove glyph, separated by comp.chip.container.gap.medium (and so on); each end SHALL be padded by comp.chip.container.padding-inline-with-icon.medium where it holds an icon or the check, by comp.chip.container.padding-inline-with-avatar.medium where it holds an avatar, by comp.chip.container.padding-inline-with-remove.medium where it holds the remove glyph, and by comp.chip.container.padding-inline.medium where the label meets it, and the row's tallest item (the label's line, a glyph's box or the avatar) SHALL be padded at the top and bottom by at least comp.chip.container.padding-block.medium (and so on for each size). The remove action's round container takes no part in this layout (CHP-18); where it lies follows from it (CHP-32).
CHP-17 THE chip SHALL set its label on one line in the text style of its size (comp.chip.label.typescale.medium, and so on) and the label color of its look, truncating it with an ellipsis only when the layout gives the chip less width than the label needs, and SHALL grow in height with larger text; the full label SHALL remain the accessible name.
CHP-18 WHERE kind is input THE chip SHALL draw a remove action at its inline end: the glyph cancel of the system's icon set, centred on a round container of the remove size of its size (comp.chip.remove.container.size.medium, and so on) with corners comp.chip.remove.container.shape. The round container is larger than the glyph and SHALL NOT take part in the layout of CHP-16: it reaches into the gap before the glyph and the padding after it, as far as CHP-32 says, without changing the chip's size. THE chip SHALL draw no mark of its own for it, and SHALL expose it as a separate element with the button role, named by removeLabel, that is not a stop in the Tab order: it is reached by pointer, touch and assistive technology, and from the keyboard through the chip itself, on which Backspace or Delete removes (CHP-19). WHEN the remove action is activated by pointer, touch, assistive technology, Enter or Space THE chip SHALL emit remove exactly once and SHALL NOT emit click.
CHP-19 WHEN kind is input and the chip itself has keyboard focus and the user presses Backspace or Delete THE chip SHALL emit remove exactly once, and SHALL NOT answer the repeats of a key that is held down, so that one press asks for one removal. WHILE kind is input, removable is true and disabled is false THE chip SHALL expose to assistive technology, on the chip itself, that Delete and Backspace remove it (the platform's keyboard-shortcut property where it has one: on the web aria-keyshortcuts="Delete Backspace"), so that a person who does not see the remove action, and who no longer meets it by Tab (CHP-18), learns the key.
CHP-20 WHILE the remove action is hovered, keyboard-focused or pressed THE chip SHALL render the remove action's container and glyph from the remove parts of the remove action's own highest-precedence state (comp.chip.outlined.remove.container.color at rest, and so on; the destructive-remove parts instead while destructiveRemove is true, CHP-27), with the precedence of CHP-3, SHALL keep the chip's container, outline, label and icon at the chip's own state (the chip is not hovered or pressed while the pointer or press is on the remove action), SHALL show the focus ring around the remove action's round container rather than around the chip, and SHALL answer a press of it as CHP-8 and CHP-9 name, within that round container.
CHP-21 WHERE kind is input THE chip SHALL give the remove action a hit area of at least sys.target.touch for touch input and sys.target.pointer for fine pointers on both axes, around its glyph, reaching toward the label no further than the gap before the glyph and extending outward, beyond the chip's visual bounds, as far as it needs — never over the label; the rest of the chip's hit area SHALL activate the chip.
CHP-22 WHEN the chip emits remove THE chip SHALL NOT remove itself or move focus: the owner removes it and moves focus to a neighbouring chip, or to the field the chips belong to.
CHP-23 THE chip SHALL be one tab stop — an input chip too: its remove action is not a stop (CHP-18), and Backspace or Delete on the chip removes it (CHP-19) — unless the component that holds it gives its set one stop and the keys to reach each chip (the chip field, CHF-11; a tag group that holds a selection, TGR-20) — and SHALL NOT handle arrow keys; moving between chips with arrow keys, choosing one or several filter chips of a set, and wrapping or scrolling a row belong to the layout that holds the chips.
CHP-24 WHEN the elevation or the colors of the chip change between states THE chip SHALL animate the change over the state duration of its size (comp.chip.motion.state-duration.small or .medium) with comp.chip.motion.state-easing, and SHALL change them at once where motion is reduced.
CHP-25 WHEN the layout direction is right-to-left THE chip SHALL mirror the order of its content and apply its paddings by logical start and end, so that the remove action sits at the left, and SHALL grow a ripple from the point of contact as in left-to-right.
CHP-26 WHILE label is not empty THE chip SHALL expose it as its accessible name, over a name the product gives it on the element (aria-label or aria-labelledby); WHILE label is empty and the product gives the chip a name of its own on the element — aria-label, or aria-labelledby naming an element — THE chip SHALL expose that name as its accessible name; WHILE label is empty and the product gives it none THE chip SHALL still render, and in debug builds report that it has no accessible name; WHILE kind is input and removeLabel is empty THE chip SHALL report the same of its remove action.
CHP-27 WHILE kind is input and destructiveRemove is true THE chip SHALL render the remove action's round container and glyph from the destructive-remove parts of its variant in the remove action's own state (comp.chip.outlined.destructive-remove.container.color and comp.chip.outlined.destructive-remove.icon.color at rest, and so on), whose hovered, focused and pressed container mixes comp.chip.outlined.destructive-remove.state-layer.color and whose hovered, focused and pressed glyph takes the error color (in the default system both are the error role); at rest and while disabled the remove action SHALL look as it does with destructiveRemove false. WHILE destructiveRemove is false THE chip SHALL render the remove action from the remove parts (CHP-20). WHERE kind is not input THE chip SHALL ignore destructiveRemove.
CHP-28 THE chip SHALL NOT make color the only sign that removing is destructive: whatever destructiveRemove is, the remove action SHALL keep the button role, its accessible name removeLabel ("Remove Ada Lovelace"), the glyph cancel, its size and its hit area, and SHALL announce nothing more and nothing less to assistive technology.
CHP-29 THE chip SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the chip is shown THE chip SHALL draw itself for the new direction at once, without a motion of its own, keeping its selection and its focus.
CHP-30 WHERE the layout direction is right-to-left THE chip SHALL draw the glyph of its icon slot mirrored left to right exactly when the system's icon set marks that glyph's name as mirrored or the product forces mirroring for that use, and unmirrored when the product forbids it: the icon helper mirrors the glyph inside its own box, about the box's vertical centre line, changing neither its place, size, color and fill nor the order CHP-25 gives. THE chip SHALL NOT mirror a glyph itself, and SHALL NOT mirror content the product gives as an image or a view of its own rather than by a glyph's name. The check, remove and cancel glyphs the chip draws itself are not marked and are never mirrored.
CHP-31 THE chip SHALL lay out its label as text of its own direction, isolated from the text around it: each takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right word, a number with its sign or unit, a telephone number and an address keep their own order in a right-to-left layout and reorder nothing beside them; THE chip SHALL still place the label and align its lines by the layout direction, not by the text's own, and SHALL cut a label that does not fit (CHP-17) at the end of its own direction, so that a left-to-right label in a right-to-left layout loses its right end and never its beginning.
CHP-32 WHERE kind is input THE chip SHALL center the remove action's round container on the remove glyph, on the chip's midline, so that it lies from the chip's inline end comp.chip.container.padding-inline-with-remove.medium less half of what comp.chip.remove.container.size.medium exceeds comp.chip.icon.size.medium by (and so on for each size), and never less than nothing; the default system's values SHALL make that distance the same as the round container's distance from the chip's top and from its bottom at the height of its size, so that the round container is inset equally on three sides, its center is the center of the chip's round end, and the two curves are concentric, as an avatar's and the round end's are at the other end. The hover, focus and press of the remove action (CHP-20) and its focus ring SHALL be drawn in that same place.
CHP-33 WHILE kind is action and href is not empty THE chip SHALL be a link and not a button: it SHALL expose the link role, named by label, with no pressed state; SHALL be the platform's own link to href (the web's anchor element; SwiftUI's Link; on Compose, Flutter and React Native an element with the platform's link semantics whose address is opened by the platform's opener), so that whatever the platform gives a link — on the web its context menu, opening in a new tab or window, copying and dragging its address, and the address shown before it is followed — is the platform's, and not the chip's to draw or to take over; and SHALL draw its container, outline, label, icon, elevation and opacity in every state from the same tokens as an action chip of its variant and size that has no destination, with no look of its own for a link and none for a destination already visited.
CHP-34 WHEN the user activates a link chip by pointer, touch or Enter THE chip SHALL emit click exactly once and then open href where target names, unless the product cancelled that click, in which case it SHALL open nothing: the product's own router has taken the navigation. It SHALL show the pressed state as CHP-8 and CHP-9 say and SHALL NOT hold the navigation back for sys.motion.duration.press-hold. THE chip SHALL NOT activate a link chip on Space, which on the web scrolls the page as it does on any link, and SHALL NOT show the pressed state for it; WHERE the platform gives its links other keys or gestures (on the web a modified or a middle click that opens a new tab, on Apple platforms a long press that previews) THE chip SHALL leave them to the platform.
CHP-35 WHILE the chip is a link and target is new THE chip SHALL open href in a new context — on the web a new tab or window that is given no handle to the page that opened it; on the other platforms outside the screen the person is on, as the platform's opener decides — and SHALL draw the glyph open_in_new of the system's icon set at its inline end, after the label, where CHP-16 lays out an input chip's remove glyph: comp.chip.container.gap.medium from the label, at comp.chip.icon.size.medium, with that end padded by comp.chip.container.padding-inline-with-icon.medium at the medium size (and each of its own size otherwise); in the icon color of its look and state (comp.chip.outlined.icon.color for outlined at rest, and so on); through the icon helper, which mirrors it in a right-to-left layout because the icon set marks its name; and hidden from assistive technology. WHILE target is new THE chip SHALL expose targetLabel as its description, read after its name and role, and WHILE targetLabel is empty SHALL still render and in debug builds report that a link that opens a new context does not say so. WHILE target is same THE chip SHALL draw no mark and SHALL ignore targetLabel.
CHP-36 WHILE kind is action and href is not empty and disabled is true THE chip SHALL NOT be a link that can be followed: it SHALL render as CHP-6 says, the mark of CHP-35 in the disabled icon color, SHALL expose the link role with the disabled state and no destination, SHALL NOT be focusable, SHALL NOT emit click, and SHALL open nothing by any means the platform gives a link (on the web the anchor carries no href, so there is no context menu entry, no drag and no new tab for it).
CHP-37 WHILE kind is filter or input THE chip SHALL ignore href, target and targetLabel and stay the chip its kind makes it, and in debug builds SHALL report that only an action chip can be a link: a control that holds a state, or that carries a second action, is not a destination. WHEN target names a value the chip does not define THE chip SHALL behave as same, and in debug builds report the unknown value. IF href is only white space, or names a script to run rather than a place to go (the web's javascript: scheme), or cannot be read as an address by the platform, THE chip SHALL be an action chip without a destination, and in the last two cases report it in debug builds.
CHP-38 WHILE kind is input and removable is false THE chip SHALL draw no remove action, SHALL have no stop for one and SHALL emit no remove, and SHALL keep the padding of a chip without one. WHILE kind is not input THE chip SHALL ignore removable.
CHP-39 WHEN a pointer or touch press lands on an input chip anywhere but on its remove action THE chip SHALL NOT let that press give the remove action focus: focus SHALL go where the platform puts it for a press on a button — to the chip itself where a pressed button takes focus, and nowhere in the chip where it takes none — so that the next Enter or Space activates the chip and never removes it. THE remove action SHALL still take focus when it is pressed itself or moved to by assistive technology or by the product, and the chip SHALL keep the Tab order of CHP-23.
CHP-40 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 chip 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 chip is invalid — except those the chip owns (aria-pressed, aria-disabled), which it SHALL move and not expose. WHEN the product removes the aria- attribute or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE chip SHALL take it off that element. WHEN the product sets an aria- attribute the chip owns THE chip SHALL report it in debug builds.

各平台名称对照

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

规格 Web SwiftUI Jetpack Compose Flutter React Native
组件 tanrei-chip TanreiChip TanreiChip TanreiChip Chip
kind · 属性 kind isSelected / onRemove onSelectedChange / onRemove TanreiChip / TanreiChip.filter / TanreiChip.input kind
variant · 属性 variant variant variant variant variant
size · 属性 size size size size size
selected · 属性 selected isSelected selected selected selected
value · 属性 value value value value value
disabled · 属性 disabled disabled enabled isEnabled disabled
href · 属性 href destination href href href
target · 属性 target target target target target
targetLabel · 属性 targetLabel targetLabel targetLabel targetSemanticLabel targetLabel
label · 属性 label title label label label
removeLabel · 属性 removeLabel removeLabel removeContentDescription removeSemanticLabel removeAccessibilityLabel
destructiveRemove · 属性 destructiveRemove destructiveRemove destructiveRemove destructiveRemove destructiveRemove
removable · 属性 removable removable removable removable removable
click · 事件 click action onClick onPressed onPress
change · 事件 change isSelected onSelectedChange onSelected onSelectedChange
remove · 事件 remove onRemove onRemove onDeleted onRemove
备注 removeLabel is reflected as the remove-label attribute. destructiveRemove is a boolean property that defaults to false, reflected as the destructive-remove attribute. The icon is the icon slot and the avatar the avatar slot. A filter chip switches its own selected and fires change, as a toggle Button does; remove is a custom event and the element stays in the DOM until the page removes it. With href an action chip's shadow root holds an <a> in place of its <button>, with the same classes, parts and listeners. The link form: href is the href attribute; target is the target attribute with the values same and new (the HTML spellings _self and _blank are read as those two; anything else is same and reported); targetLabel is the target-label attribute. Web-only, forwarded to the anchor and to nothing else: rel (to which new adds noopener) and download. Cancelling click (preventDefault()) cancels the navigation, which is how a client-side router takes it; a modified or a middle click is the browser's. A disabled link is the anchor without href, with role="link" and aria-disabled="true", as the navigation drawer's destination already is. Space is not handed to the press controller. element.click() follows the link. removable is a boolean property that defaults to true; its attribute is the inverse, not-removable, because an HTML boolean attribute cannot default to true. The remove action's <button> has tabindex="-1" (CHP-18): pressable by pointer and assistive technology, focusable by focus(), passed by Tab; while the chip removes, its own button or link carries aria-keyshortcuts="Delete Backspace" (CHP-19). A component that holds chips as a set and gives the set one stop (CHP-23, the chip field) takes each chip and its remove action out of the Tab order in the way the toolbar holds its Buttons. A mouse press anywhere on an input chip but its remove action takes the remove action's tabindex away until that press has placed focus (CHP-39): Safari on macOS gives a pressed <button> no focus and delegates the press to the first element with a tabindex in the shadow root, which would otherwise be the remove action. Kind is chosen by initializer: init(_:action:) is an action chip, init(_:isSelected:) (a Binding<Bool>) a filter chip, init(_:removeLabel:destructiveRemove:action:onRemove:) an input chip, with destructiveRemove defaulting to false. The remove action is a second Button placed beside the chip's own, never inside it, so VoiceOver reaches it as its own element. disabled follows .disabled(_:). The link form, not built on this platform yet: the initializer init(_:destination:), which takes a URL in place of action: (the filter and input initializers take none) draws the same control as a SwiftUI Link, so the address is opened by the environment's openURL — an app that routes addresses itself provides an OpenURLAction, which is how the navigation is cancelled — and VoiceOver reads the link trait. target: TanreiLinkTarget = .same and targetLabel (the accessibility hint) are parameters of that initializer alone. Not built on this platform yet: the remove action out of the focus order, with the keys of CHP-19 told to assistive technology — here it is still a second stop after the chip (CHP-18, CHP-19, CHP-23); removable, default true — an input chip that draws no remove action while it is false (CHP-38); and a holder that gives a set of chips one stop takes each chip's own stops away (CHP-23). Lagging behind CHP-14 (the check drawn at comp.chip.check.weight): its check is drawn at sys.icon.weight. Kind is chosen by overload: onClick alone is an action chip, selected + onSelectedChange a filter chip, onClick + onRemove an input chip. enabled is the inverse flag. The remove action is its own semantics node with Role.Button. The link form, not built on this platform yet: the action overload with href: String (the filter and input overloads take none) opens the address through LocalUriHandler.current after onClick; an app that routes addresses itself provides its own UriHandler. Compose has no link role for a clickable element (only link annotations inside text), so the node drops Role.Button and carries no role, and the README says so. target: TanreiLinkTarget = TanreiLinkTarget.Same; targetLabel is appended to the node's state description. Not built on this platform yet: the remove action out of the focus order, with the keys of CHP-19 told to assistive technology — here it is still a second stop after the chip (CHP-18, CHP-19, CHP-23); removable, default true — an input chip that draws no remove action while it is false (CHP-38); and a holder that gives a set of chips one stop takes each chip's own stops away (CHP-23). Lagging behind CHP-14 (the check drawn at comp.chip.check.weight): its check is drawn at sys.icon.weight. Kind is chosen by constructor: the default constructor is an action chip, .filter a filter chip, .input an input chip (Flutter's own FilterChip and InputChip names for the callbacks). isEnabled: false disables it, as Flutter's chips do. The remove action is its own Semantics node with the button flag. The link form, not built on this platform yet: the TanreiChip.link constructor (href is a Uri) has Semantics(link: true) with the address as its linkUrl. The package has no plugin dependency, so the address is opened by the nearest TanreiLinkOpener, an inherited widget the app provides once (calling url_launcher, or its own router); without one the control opens nothing and reports it in debug builds. target is a TanreiLinkTarget; targetSemanticLabel is the semantics hint. Not built on this platform yet: the remove action out of the focus order, with the keys of CHP-19 told to assistive technology — here it is still a second stop after the chip (CHP-18, CHP-19, CHP-23); removable, default true — an input chip that draws no remove action while it is false (CHP-38); and a holder that gives a set of chips one stop takes each chip's own stops away (CHP-23). Lagging behind CHP-14 (the check drawn at comp.chip.check.weight): its check is drawn at sys.icon.weight. The icon is the icon prop (a glyph name) and the avatar the avatar prop (an image source). The remove action is a second accessible Pressable with the button role, placed beside the chip's own rather than inside it, so a screen reader reaches it as its own element. The link form, not built on this platform yet: an action Chip with href has accessibilityRole="link" and opens the address with Linking.openURL after onPress, unless onPress called event.preventDefault(). target is "same" | "new"; targetLabel is the accessibilityHint. Not built on this platform yet: the remove action out of the focus order, with the keys of CHP-19 told to assistive technology — here it is still a second stop after the chip (CHP-18, CHP-19, CHP-23); removable, default true — an input chip that draws no remove action while it is false (CHP-38); and a holder that gives a set of chips one stop takes each chip's own stops away (CHP-23). Lagging behind CHP-14 (the check drawn at comp.chip.check.weight): its check is drawn at sys.icon.weight.

令牌

令牌 引用 解析值(浅色)
--tanrei-comp-chip-avatar-shape sys.shape.corner.full 9999px
--tanrei-comp-chip-avatar-size-medium — 24px
--tanrei-comp-chip-avatar-size-small — 16px
--tanrei-comp-chip-check-weight sys.icon.mark.weight 500
--tanrei-comp-chip-container-gap-medium sys.space.space100 8px
--tanrei-comp-chip-container-gap-small sys.space.space50 4px
--tanrei-comp-chip-container-height-medium — 32px
--tanrei-comp-chip-container-height-small — 24px
--tanrei-comp-chip-container-padding-block-medium — 4px
--tanrei-comp-chip-container-padding-block-small sys.space.space25 2px
--tanrei-comp-chip-container-padding-inline-with-avatar-medium sys.space.space50 4px
--tanrei-comp-chip-container-padding-inline-with-avatar-small sys.space.space50 4px
--tanrei-comp-chip-container-padding-inline-with-icon-medium sys.space.space100 8px
--tanrei-comp-chip-container-padding-inline-with-icon-small sys.space.space75 6px
--tanrei-comp-chip-container-padding-inline-with-remove-medium sys.space.space50 4px
--tanrei-comp-chip-container-padding-inline-with-remove-small sys.space.space25 2px
--tanrei-comp-chip-container-padding-inline-medium sys.space.space200 16px
--tanrei-comp-chip-container-padding-inline-small sys.space.space100 8px
--tanrei-comp-chip-container-shape sys.shape.corner.full 9999px
--tanrei-comp-chip-disabled-opacity — 1
--tanrei-comp-chip-elevated-container-color sys.color.surface-container-lowest #ffffff
--tanrei-comp-chip-elevated-container-elevation sys.elevation.level2 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-chip-elevated-destructive-remove-container-color — #000000 · 0%
--tanrei-comp-chip-elevated-destructive-remove-icon-color comp.chip.elevated.remove.icon.color #4b4b4b
--tanrei-comp-chip-elevated-destructive-remove-state-layer-color sys.color.error #cd262f
--tanrei-comp-chip-elevated-disabled-container-color — #1b1b1b · 12%
--tanrei-comp-chip-elevated-disabled-container-elevation —
--tanrei-comp-chip-elevated-disabled-destructive-remove-container-color — #000000 · 0%
--tanrei-comp-chip-elevated-disabled-destructive-remove-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-destructive-remove-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-disabled-remove-container-color — #000000 · 0%
--tanrei-comp-chip-elevated-disabled-remove-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-remove-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-selected-container-color — #1b1b1b · 12%
--tanrei-comp-chip-elevated-disabled-selected-container-elevation —
--tanrei-comp-chip-elevated-disabled-selected-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-selected-label-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-disabled-selected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-elevated-focused-container-color — #e8e8e8
--tanrei-comp-chip-elevated-focused-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-chip-elevated-focused-destructive-remove-container-color — #cd262f · 10%
--tanrei-comp-chip-elevated-focused-destructive-remove-icon-color — #cd262f
--tanrei-comp-chip-elevated-focused-destructive-remove-state-layer-color — #cd262f
--tanrei-comp-chip-elevated-focused-icon-color — #4b4b4b
--tanrei-comp-chip-elevated-focused-label-color — #1b1b1b
--tanrei-comp-chip-elevated-focused-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-focused-remove-container-color — #4b4b4b · 10%
--tanrei-comp-chip-elevated-focused-remove-icon-color — #4b4b4b
--tanrei-comp-chip-elevated-focused-remove-state-layer-color — #4b4b4b
--tanrei-comp-chip-elevated-focused-selected-container-color — #dadada
--tanrei-comp-chip-elevated-focused-selected-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-chip-elevated-focused-selected-icon-color — #262626
--tanrei-comp-chip-elevated-focused-selected-label-color — #262626
--tanrei-comp-chip-elevated-focused-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-focused-selected-state-layer-color — #262626
--tanrei-comp-chip-elevated-focused-state-layer-color — #1b1b1b
--tanrei-comp-chip-elevated-hovered-container-color — #ededed
--tanrei-comp-chip-elevated-hovered-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 1px 0px rgb(0 0 0 / 0.04), 0px 4px 8px -4px rgb(0 0 0 / 0.04), 0px 16px 24px -8px rgb(0 0 0 / 0.06)
--tanrei-comp-chip-elevated-hovered-destructive-remove-container-color — #cd262f · 8%
--tanrei-comp-chip-elevated-hovered-destructive-remove-icon-color — #cd262f
--tanrei-comp-chip-elevated-hovered-destructive-remove-state-layer-color — #cd262f
--tanrei-comp-chip-elevated-hovered-icon-color — #4b4b4b
--tanrei-comp-chip-elevated-hovered-label-color — #1b1b1b
--tanrei-comp-chip-elevated-hovered-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-hovered-remove-container-color — #4b4b4b · 8%
--tanrei-comp-chip-elevated-hovered-remove-icon-color — #4b4b4b
--tanrei-comp-chip-elevated-hovered-remove-state-layer-color — #4b4b4b
--tanrei-comp-chip-elevated-hovered-selected-container-color — #dedede
--tanrei-comp-chip-elevated-hovered-selected-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 1px 0px rgb(0 0 0 / 0.04), 0px 4px 8px -4px rgb(0 0 0 / 0.04), 0px 16px 24px -8px rgb(0 0 0 / 0.06)
--tanrei-comp-chip-elevated-hovered-selected-icon-color — #262626
--tanrei-comp-chip-elevated-hovered-selected-label-color — #262626
--tanrei-comp-chip-elevated-hovered-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-hovered-selected-state-layer-color — #262626
--tanrei-comp-chip-elevated-hovered-state-layer-color — #1b1b1b
--tanrei-comp-chip-elevated-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-chip-elevated-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-chip-elevated-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-pressed-container-color — #e8e8e8
--tanrei-comp-chip-elevated-pressed-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-chip-elevated-pressed-destructive-remove-container-color — #cd262f · 10%
--tanrei-comp-chip-elevated-pressed-destructive-remove-icon-color — #cd262f
--tanrei-comp-chip-elevated-pressed-destructive-remove-state-layer-color — #cd262f
--tanrei-comp-chip-elevated-pressed-icon-color — #4b4b4b
--tanrei-comp-chip-elevated-pressed-label-color — #1b1b1b
--tanrei-comp-chip-elevated-pressed-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-pressed-remove-container-color — #4b4b4b · 10%
--tanrei-comp-chip-elevated-pressed-remove-icon-color — #4b4b4b
--tanrei-comp-chip-elevated-pressed-remove-state-layer-color — #4b4b4b
--tanrei-comp-chip-elevated-pressed-selected-container-color — #dadada
--tanrei-comp-chip-elevated-pressed-selected-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-chip-elevated-pressed-selected-icon-color — #262626
--tanrei-comp-chip-elevated-pressed-selected-label-color — #262626
--tanrei-comp-chip-elevated-pressed-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-pressed-selected-state-layer-color — #262626
--tanrei-comp-chip-elevated-pressed-state-layer-color — #1b1b1b
--tanrei-comp-chip-elevated-remove-container-color — #000000 · 0%
--tanrei-comp-chip-elevated-remove-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-chip-elevated-remove-state-layer-color comp.chip.elevated.remove.icon.color #4b4b4b
--tanrei-comp-chip-elevated-selected-container-color sys.color.secondary-container #eeeeee
--tanrei-comp-chip-elevated-selected-container-elevation sys.elevation.level2 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-chip-elevated-selected-icon-color sys.color.on-secondary-container #262626
--tanrei-comp-chip-elevated-selected-label-color sys.color.on-secondary-container #262626
--tanrei-comp-chip-elevated-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-elevated-selected-state-layer-color comp.chip.elevated.selected.label.color #262626
--tanrei-comp-chip-elevated-state-layer-color comp.chip.elevated.label.color #1b1b1b
--tanrei-comp-chip-focused-opacity — 1
--tanrei-comp-chip-hovered-opacity — 1
--tanrei-comp-chip-icon-size-medium sys.icon.medium 24px
--tanrei-comp-chip-icon-size-small sys.icon.small 20px
--tanrei-comp-chip-motion-feedback-duration sys.motion.role.feedback 450ms
--tanrei-comp-chip-motion-selection-duration sys.motion.role.selection.mark 250ms
--tanrei-comp-chip-motion-selection-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-chip-motion-state-duration-medium sys.motion.role.state.default 250ms
--tanrei-comp-chip-motion-state-duration-small sys.motion.role.state.compact 200ms
--tanrei-comp-chip-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-chip-outline-width sys.border.hairline 1px
--tanrei-comp-chip-outlined-container-color — #000000 · 0%
--tanrei-comp-chip-outlined-container-elevation sys.elevation.level0
--tanrei-comp-chip-outlined-destructive-remove-container-color — #000000 · 0%
--tanrei-comp-chip-outlined-destructive-remove-icon-color comp.chip.outlined.remove.icon.color #4b4b4b
--tanrei-comp-chip-outlined-destructive-remove-state-layer-color sys.color.error #cd262f
--tanrei-comp-chip-outlined-disabled-container-color — #000000 · 0%
--tanrei-comp-chip-outlined-disabled-container-elevation —
--tanrei-comp-chip-outlined-disabled-destructive-remove-container-color — #000000 · 0%
--tanrei-comp-chip-outlined-disabled-destructive-remove-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-destructive-remove-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-outline-color — #000000 · 8%
--tanrei-comp-chip-outlined-disabled-remove-container-color — #000000 · 0%
--tanrei-comp-chip-outlined-disabled-remove-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-remove-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-selected-container-color — #1b1b1b · 12%
--tanrei-comp-chip-outlined-disabled-selected-container-elevation —
--tanrei-comp-chip-outlined-disabled-selected-icon-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-selected-label-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-outlined-disabled-selected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-chip-outlined-focused-container-color — #1b1b1b · 10%
--tanrei-comp-chip-outlined-focused-container-elevation —
--tanrei-comp-chip-outlined-focused-destructive-remove-container-color — #cd262f · 10%
--tanrei-comp-chip-outlined-focused-destructive-remove-icon-color — #cd262f
--tanrei-comp-chip-outlined-focused-destructive-remove-state-layer-color — #cd262f
--tanrei-comp-chip-outlined-focused-icon-color — #4b4b4b
--tanrei-comp-chip-outlined-focused-label-color — #1b1b1b
--tanrei-comp-chip-outlined-focused-outline-color — #000000 · 8%
--tanrei-comp-chip-outlined-focused-remove-container-color — #4b4b4b · 10%
--tanrei-comp-chip-outlined-focused-remove-icon-color — #4b4b4b
--tanrei-comp-chip-outlined-focused-remove-state-layer-color — #4b4b4b
--tanrei-comp-chip-outlined-focused-selected-container-color — #dadada
--tanrei-comp-chip-outlined-focused-selected-container-elevation —
--tanrei-comp-chip-outlined-focused-selected-icon-color — #262626
--tanrei-comp-chip-outlined-focused-selected-label-color — #262626
--tanrei-comp-chip-outlined-focused-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-outlined-focused-selected-state-layer-color — #262626
--tanrei-comp-chip-outlined-focused-state-layer-color — #1b1b1b
--tanrei-comp-chip-outlined-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-chip-outlined-hovered-container-elevation —
--tanrei-comp-chip-outlined-hovered-destructive-remove-container-color — #cd262f · 8%
--tanrei-comp-chip-outlined-hovered-destructive-remove-icon-color — #cd262f
--tanrei-comp-chip-outlined-hovered-destructive-remove-state-layer-color — #cd262f
--tanrei-comp-chip-outlined-hovered-icon-color — #4b4b4b
--tanrei-comp-chip-outlined-hovered-label-color — #1b1b1b
--tanrei-comp-chip-outlined-hovered-outline-color — #000000 · 8%
--tanrei-comp-chip-outlined-hovered-remove-container-color — #4b4b4b · 8%
--tanrei-comp-chip-outlined-hovered-remove-icon-color — #4b4b4b
--tanrei-comp-chip-outlined-hovered-remove-state-layer-color — #4b4b4b
--tanrei-comp-chip-outlined-hovered-selected-container-color — #dedede
--tanrei-comp-chip-outlined-hovered-selected-container-elevation —
--tanrei-comp-chip-outlined-hovered-selected-icon-color — #262626
--tanrei-comp-chip-outlined-hovered-selected-label-color — #262626
--tanrei-comp-chip-outlined-hovered-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-outlined-hovered-selected-state-layer-color — #262626
--tanrei-comp-chip-outlined-hovered-state-layer-color — #1b1b1b
--tanrei-comp-chip-outlined-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-chip-outlined-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-chip-outlined-outline-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-chip-outlined-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-chip-outlined-pressed-container-elevation —
--tanrei-comp-chip-outlined-pressed-destructive-remove-container-color — #cd262f · 10%
--tanrei-comp-chip-outlined-pressed-destructive-remove-icon-color — #cd262f
--tanrei-comp-chip-outlined-pressed-destructive-remove-state-layer-color — #cd262f
--tanrei-comp-chip-outlined-pressed-icon-color — #4b4b4b
--tanrei-comp-chip-outlined-pressed-label-color — #1b1b1b
--tanrei-comp-chip-outlined-pressed-outline-color — #000000 · 8%
--tanrei-comp-chip-outlined-pressed-remove-container-color — #4b4b4b · 10%
--tanrei-comp-chip-outlined-pressed-remove-icon-color — #4b4b4b
--tanrei-comp-chip-outlined-pressed-remove-state-layer-color — #4b4b4b
--tanrei-comp-chip-outlined-pressed-selected-container-color — #dadada
--tanrei-comp-chip-outlined-pressed-selected-container-elevation —
--tanrei-comp-chip-outlined-pressed-selected-icon-color — #262626
--tanrei-comp-chip-outlined-pressed-selected-label-color — #262626
--tanrei-comp-chip-outlined-pressed-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-outlined-pressed-selected-state-layer-color — #262626
--tanrei-comp-chip-outlined-pressed-state-layer-color — #1b1b1b
--tanrei-comp-chip-outlined-remove-container-color — #000000 · 0%
--tanrei-comp-chip-outlined-remove-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-chip-outlined-remove-state-layer-color comp.chip.outlined.remove.icon.color #4b4b4b
--tanrei-comp-chip-outlined-selected-container-color sys.color.secondary-container #eeeeee
--tanrei-comp-chip-outlined-selected-container-elevation sys.elevation.level0
--tanrei-comp-chip-outlined-selected-icon-color sys.color.on-secondary-container #262626
--tanrei-comp-chip-outlined-selected-label-color sys.color.on-secondary-container #262626
--tanrei-comp-chip-outlined-selected-outline-color — #000000 · 0%
--tanrei-comp-chip-outlined-selected-state-layer-color comp.chip.outlined.selected.label.color #262626
--tanrei-comp-chip-outlined-state-layer-color comp.chip.outlined.label.color #1b1b1b
--tanrei-comp-chip-pressed-opacity — 1
--tanrei-comp-chip-remove-container-shape sys.shape.corner.full 9999px
--tanrei-comp-chip-remove-container-size-medium — 28px
--tanrei-comp-chip-remove-container-size-small — 24px
--tanrei-comp-chip-label-typescale-medium sys.typescale.label 500 14px/1.4286
--tanrei-comp-chip-label-typescale-small sys.typescale.label-small 500 12px/1.3333

测试样例

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

生成自 specs/chip.spec.json, tokens/comp/chip.tokens.json, design/tokens/

指南

何时使用

纸片用于排成一行的、彼此相关的小选择:列表上方的筛选条件、消息下方的建议回复、电子邮件的收件人。一行十个的时候,按钮显得太重,复选框看起来像表单的一部分,而徽标或 Tag 又不能按。

如何选择种类

kind 决定纸片做什么,以及它的角色和部件。它不改变外观。

种类 它是什么 用途
操作(默认) 一个按钮;带 href 时是一个链接 与内容相关的辅助操作,例如日期旁边的“添加到日历”;或者一条建议,例如回复“听起来不错”。有目的地时,是通往相关页面的快捷入口。
筛选 一个可开可关的按钮 缩小列表的范围:“素食”“30 分钟以内”。打开时,它显示一个对勾和选中的颜色。
输入 用户输入的一项,带有移除操作 收件人、标签、搜索词。它可以显示头像来代替图标。

Material Design 3 有 4 个纸片家族。Tanrei 只算 3 个种类:它的辅助(assist)纸片和建议(suggestion)纸片都是带标签和可选图标的按钮,所以两者都属于 action 种类。想让建议看起来更低调的设计系统,可以为它们添加自己的变体。

如何选择变体和尺寸

变体 用途
Outlined(默认) 放在素净表面上的纸片。边线是细线:它不动声色地把纸片区分出来,含义则由标签承载。
Elevated 放在照片、地图或杂乱背景之上的纸片,在那里只靠一条边线会被淹没。

大多数纸片使用 medium(32);在密集的地方使用 small(24),例如字段里已输入的项,或表格上方的筛选条件。表格行里的静态标签则是 Tag。两种尺寸都满足最小目标。

纸片、Tag 还是徽标

纸片是控件:它可以被按下、切换或移除,能获得焦点,并且有状态。标签(Tag)是某一项上的静态标签,例如状态或类别,从不响应指针。徽标是附着在另一个元素角上的圆点或计数。

  • 如果它会被按下、选中或移除,使用纸片。
  • 如果它只是说明它所在的那一项,使用 Tag。
  • 如果它是叠在别的东西上面的数字或圆点,使用徽标。

不要把什么都不做的纸片当作标签用,也不要为了把纸片变成标签而禁用它。

有目的地的纸片是链接

有目的地的控件就是链接。只负责把用户带到某处的操作纸片接收 href,这时它是真正的链接,而不是按钮:在 Web 上是一个锚元素,可以在新标签页中打开,地址也可以复制。它在每种状态下都和操作纸片一模一样,并且没有已访问状态。

  • Enter 会打开链接纸片。Space 不会:它的作用与在任何链接上一样。
  • 先触发 click,然后才打开目的地。取消这次点击,就可以像客户端路由那样自己接管导航。
  • target 设为 new 时,纸片在标签后面显示 open_in_new 图标,并且需要 targetLabel,也就是在名称之后朗读的文字,例如“在新标签页中打开”。Tanrei 不自带任何文字。
  • 禁用的链接纸片无法打开,并且退出焦点顺序。
  • 只有操作纸片可以是链接。筛选纸片持有状态,输入纸片带有第二个操作,所以两者都忽略 href,调试构建会报告这一点。

移除操作

输入纸片以移除操作结尾:填充样式的 cancel 图标,放在一个比它略大的圆形容器上;悬停、聚焦和按下时,这个容器显示为图标周围的一圈圆环。在默认系统中,这个圆形容器与纸片的圆形末端同心,到末端的距离与到顶部和底部的距离相等。它不占用自己的空间,所以不会让纸片变宽或变高。移除操作是一个独立的按钮,有自己的名称,自己的悬停、聚焦和按下状态,以及自己的点击区域;这个区域向外延伸,绝不盖住标签。按下它,或者在纸片获得焦点时按 Backspace 或 Delete,都是请求移除这个纸片。纸片绝不会移除自己。 由你的代码移除这一项并移动焦点:移到下一个纸片、上一个纸片,或者这些纸片所属的字段。

移除操作可以呈现为破坏性操作(destructiveRemove,默认关闭)。静止时,它的样子和没有这个标志时一样。悬停、聚焦或按下时,它的图标和状态层采用错误颜色。大多数移除拿掉的是用户一步就能恢复的东西,例如已应用的筛选条件或搜索词,所以除非你设置这个标志,移除操作都是普通外观。当移除会丢失一步无法恢复的东西时(例如附件,或已经发出邀请的收件人),再设置它。在 Web 上,这个属性是 destructive-remove。这个标志只改变颜色,不改变别的,并且在其他种类上会被忽略。

暂时不能移除的输入纸片,例如只读字段里的一个条目,不绘制移除操作:把 removable 设为 false。这样就不会有一个什么都不做的操作摆在那里,Backspace 和 Delete 不请求任何事情,纸片的内边距也与不带移除操作的纸片相同。在 Web 上,这个属性是反过来的 not-removable。它在其他种类上会被忽略。

使用它的组件是纸片字段,其中每个条目都是一个小号的输入纸片。在那里,字段给整组纸片一个 Tab 停靠点和它自己的按键,纸片请求移除时由它来移除,并移动焦点。在 Web 上,以这种方式容纳纸片的组件会在每个纸片上设置 tabindex="-1",这样纸片及其移除操作都退出 Tab 顺序,但两者仍然可以获得焦点。

推荐

  • 标签保持简短,一到三个词。纸片保持在一行之内,只有布局给它的宽度不够时,才以省略号截断标签。
  • 一行纸片只使用一种变体。
  • 在移除标签中说出这一项的名称:写“移除李华”,而不是“移除”。
  • 只有当移除会丢失一步无法恢复的东西时(例如附件),才给移除操作加上破坏性外观。如果这一项一步就能恢复(例如筛选条件或搜索词),就保持普通外观。
  • 在 Web 上,把一行纸片放进标签组:它把纸片排成会换行的若干行,保持它们之间的距离,并把它们作为一个列表暴露出来。在其他平台上,以及需要滚动的一行纸片,布局由你自己来做。

避免

  • 不要把筛选纸片用于立即生效且独立存在的设置。请使用开关。
  • 不要把纸片用于屏幕的主要操作。请使用按钮或主操作按钮。
  • 不要在操作纸片的点击处理函数里导航。要去往某处的纸片应当接收 href。
  • 不要在同一行里混用不同的种类,例如把筛选条件放在建议操作旁边。
  • 不要给筛选纸片一个随状态变化的标签。状态会另行朗读。

无障碍

角色
button (link while an action chip has href)
标注
label is the chip's accessible name. An input chip's remove action is a separate button named by removeLabel, which names the item and the action ("Remove Ada Lovelace"), with or without destructiveRemove. The icon, the check, the cancel glyph and the avatar are decorative: a filter chip's state is announced from its pressed state, never from the check.
最小目标
即使外观尺寸更小,点击区域也满足最小目标尺寸。 minimum 32px · pointer 44px · spacing 8px · touch 48px

键盘操作

按键 动作
Tab Moves focus to the chip. An input chip is one stop too: its remove action is not in the Tab order, and is reached by pointer, touch and assistive technology.
Enter / Space Activates the chip (a filter chip switches selected); on the remove action, removes the input chip. A link chip is activated by Enter alone: Space does what it does on any link (on the web it scrolls the page).
Backspace / Delete On a focused input chip, removes it (CHP-19). The chip tells assistive technology of these keys (on the web aria-keyshortcuts="Delete Backspace"), since its remove action is not met by Tab.

备注

  • A filter chip is a button with a pressed state (aria-pressed on the web, the selected trait on Apple platforms, a toggleable state on Android), as a toggle Button is, not a checkbox.
  • An input chip is two elements on every platform: the chip (role button) and its remove action (role button, named by removeLabel). On the web the remove action is a second `<button>` in the shadow root; on the other platforms it is its own accessibility element with its own hit area. It is one Tab stop: the remove action stays out of the Tab order, as the delete icon of MUI's Chip, the remove button of React Aria's TagGroup and the dismiss of Fluent's Tag do, and the keyboard removes the chip with Backspace or Delete on the chip itself, as a person who removes several entries in a row expects — one press each, without a second stop per chip to pass.
  • The chip does not group itself. A row of chips is laid out in wrapping lines, exposed as one list and given focus after a removal by the Tag group (TGR-1, TGR-7, TGR-9), or by the layout that holds the chips; one Tab stop with arrow keys, and single or multiple selection among filter chips, are a Tag group's that holds a selection (TGR-17 to TGR-21), or a Toolbar's.
  • destructiveRemove adds a color, never a meaning: the name removeLabel, the cancel glyph and the button role already say what the action does, so a person who cannot tell the error color from the resting one loses nothing (WCAG 1.4.1). The error glyph is a graphical object and keeps at least 3:1 against the chip's container in every scheme of the default system: the error role is held to 4.5:1 on every surface role by the token invariants, and the outlined chip shows a surface through its clear container while the elevated one is surface-container-lowest; under the state layer the lowest ratio is 3.98:1 (light scheme, on surface-container-highest, at the focus and pressed opacity).
  • The label does not wrap; at 200% text size the chip grows in height and the layout that holds a row lets it wrap to the next line or scroll.
  • 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.
  • In the default system the outline of an outlined chip is the hairline (the divider's color), far below 3:1 against the surface. That is within WCAG 1.4.11: the edge is not what identifies the chip. Its label is text held to 4.5:1, a selected filter chip is shown by its fill and its check, and focus by the focus ring. A product whose chips must show a 3:1 edge sets comp.chip.outlined.outline.color to sys.color.outline in a layer.
  • An action chip with a destination is a link: the link role, the platform's own link, Enter and not Space. A filter chip and an input chip take no destination. No visited state is drawn.
  • A link chip that opens a new context shows the open_in_new mark after its label and says so through targetLabel; the words are the product's.

生成自 specs/chip.spec.json, tokens/sys/measure.tokens.json

组件负责的部分

  • 每个纸片都暴露按钮角色、作为名称的标签,以及它的禁用状态。图标、对勾和头像对辅助技术隐藏。
  • 带 href 的操作纸片改为暴露链接角色,名称相同。在新标签页中打开的链接上的标记对辅助技术隐藏;targetLabel 是这个链接的描述,在名称和角色之后朗读。
  • 筛选纸片还像切换按钮那样暴露它是否打开:在 Web 上是按下状态,在 Apple 的平台上是选中特征,在 Android 上是可切换状态。它的状态绝不根据对勾来朗读。
  • 输入纸片是两个元素:纸片,以及它的移除操作——一个由移除标签命名的独立按钮;但它在焦点顺序中只是一个停靠点:Tab 到达纸片后会跳过移除操作,移除操作仍然可以用指针、触摸和辅助技术按下。在获得焦点的纸片上按 Backspace 或 Delete,是请求移除它,纸片也会把这一点告诉辅助技术(在 Web 上通过 aria-keyshortcuts)。
  • Enter 和 Space 激活纸片;移除操作获得焦点时,激活的是移除操作。链接纸片只由 Enter 激活。它们各自显示自己的焦点指示,并且只在键盘聚焦时显示。
  • 纸片的点击区域在两种尺寸下都满足最小目标。移除操作有自己的点击区域,同样满足最小目标,并且不盖住标签。
  • 破坏性的移除外观绝不是唯一的线索。无论 destructiveRemove 是开还是关,移除操作都保持它的角色、名称、图标、大小和点击区域,也不会多朗读任何内容。它的名称“移除李华”说明了它做什么。
  • 即使标签被截断,完整的标签仍然是无障碍名称。文本放大时,纸片的高度增加,并且仍然是胶囊形。
  • 在默认系统中,outlined 纸片的边线是细线,它相对于表面的对比度低于 3:1。用来识别纸片的并不是边线:标签是对比度充足的文本,选中的筛选纸片显示填充和对勾,聚焦时显示焦点环。需要 3:1 边线的产品,可以在层里把 comp.chip.outlined.outline.color 设为 sys.color.outline。
  • 调试构建会报告:缺少标签;输入纸片缺少移除标签;在新标签页中打开的链接没有 targetLabel;筛选纸片或输入纸片上设置了目的地;以及未知的种类——它会按操作纸片来渲染和工作。

已知的平台差距:

  • React Native:Backspace 和 Delete 是通过一个按键事件传递的,这个事件在 iOS 和 Android 上能否送达,还没有在真机上验证过。无论如何,移除操作都可以用 Tab 到达。
  • React Native:VoiceOver 和 TalkBack 是否按这个顺序把纸片和它的移除操作当作两个元素到达,以及移除操作的点击区域,都只在没有真机的情况下测试过。在 Android 上,点击区域无法超出父视图,所以请在一行输入纸片的周围留出空间。
  • Flutter:移除操作的点击区域超出纸片的末端,而 Flutter 通过加宽输入纸片的布局来为它腾出空间,所以一行输入纸片的间隔会略宽一些。
  • Compose:移除操作的点击区域超出纸片的末端,但不会加宽纸片,所以紧跟在后面的纸片会在两者重叠的地方接收按下。那里的触摸、经由平台按键处理的按键,以及 TalkBack,都只在 Robolectric 下测试过,还没有在真机上测试。
  • SwiftUI:纸片上的 Backspace 和 Delete,以及从纸片到移除操作的 Tab,都按各自的部分测试过,但还没有做端到端的驱动测试。在按下会使控件变暗的系统(cupertino)中,移除图标比纸片晚一帧变暗。
  • 文本放大时,SwiftUI 和 Compose 让图标、对勾、移除图标和头像保持各自的大小,而标签会变大。它们是否应该随标签一起变大,尚未决定。
  • SwiftUI、Compose、Flutter 和 React Native 提供的是上一版,输入纸片的移除操作仍是纸片之后的第二个停靠点,也还没有告诉辅助技术 Backspace 和 Delete 可以移除它。它们赶上时会把移除操作移出焦点顺序。
  • Web 按规格给出的名称(CHP-13、CHP-18),把对勾和移除图标画成图标集里的 check 和 cancel。SwiftUI、Compose、Flutter 和 React Native 提供的是上一版,仍然用 close 绘制移除操作(Flutter 用的是 Material Symbols 的码位);没有图标字体时,Compose 和 SwiftUI 把对勾和叉号画成各自的矢量图形。它们跟上之后,都会按名称绘制 cancel 和 check,不再使用自己的矢量图形。

需要你做的事

  • 用户移除输入纸片时,移除它,并把焦点移到相邻的纸片或字段上。纸片发出请求之后就什么都不做了。
  • 给每个输入纸片一个说出其项目名称的移除标签。一行里好几个“移除”按钮是无法区分的。
  • 给在新标签页或新窗口中打开的链接纸片一个 targetLabel,使用你的产品所用的语言。
  • 给一行筛选纸片一个可见的标题或组名,说明它们筛选的是什么。
  • 如果需要一行纸片的键盘模型,例如用方向键在纸片之间移动,请自行提供。每个纸片只处理 Tab、Enter、Space,以及输入纸片上的 Backspace 和 Delete。