跳到主要内容
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.button
层级
基础

概览

Web
tanrei-button
schedule
SwiftUI
TanreiButton
schedule
Jetpack Compose
TanreiButton
schedule
Flutter
TanreiButton
schedule
React Native
Button
schedule

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

变体

FilledTonalElevatedOutlinedText

交互演示

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

用法

Web 和 React 的示例展示的是当前的规格。SwiftUI、Compose、Flutter 和 React Native 待跟进:它们提供的仍是按钮的上一版,其中纯图标的形态和切换功能属于另一个独立的图标按钮,也没有纯图标按钮的宽度和链接形态。在它们跟上之前,这些平台的示例展示的是那一版。

<tanrei-button variant="filled">部署</tanrei-button>
<tanrei-button variant="outlined" size="small">取消</tanrei-button>
<tanrei-button busy busy-label="正在部署">部署</tanrei-button>

<!-- 有 href 时它是链接,画出来仍是同一个按钮。 -->
<tanrei-button variant="tonal" href="/pricing/">查看价格</tanrei-button>

<script type="module">
  import '@tanrei/web/components/button';
  document.querySelector('tanrei-button').addEventListener('click', deploy);
</script>
import { Button } from '@tanrei/react';

<Button variant="filled" onClick={deploy}>部署</Button>
<Button variant="outlined" size="small" onClick={cancel}>取消</Button>
<Button disabled>正在部署…</Button>

// 有 href 时它是链接,画出来仍是同一个按钮。
<Button variant="tonal" href="/pricing/">查看价格</Button>
import Tanrei

TanreiButton("部署", variant: .filled) { deploy() }
TanreiButton("取消", variant: .outlined, size: .small) { cancel() }

// 禁用状态遵循 SwiftUI 的环境值。
TanreiButton("正在部署…") {}
  .disabled(true)
import io.github.chnotchy.tanrei.components.*

TanreiButton(text = "部署", onClick = ::deploy, variant = TanreiButtonVariant.Filled)
TanreiButton(text = "取消", onClick = ::cancel, variant = TanreiButtonVariant.Outlined, size = TanreiButtonSize.Small)

// Compose 使用含义相反的标志。
TanreiButton(text = "正在部署…", onClick = {}, enabled = false)
import 'package:tanrei/tanrei.dart';

TanreiButton(variant: TanreiButtonVariant.filled, onPressed: deploy, child: const Text('部署'));
TanreiButton(variant: TanreiButtonVariant.outlined, size: TanreiButtonSize.small, onPressed: cancel, child: const Text('取消'));

// onPressed 为 null 时,按钮被禁用。
TanreiButton(onPressed: null, child: const Text('正在部署…'));
import { Button } from '@tanrei/react-native';

<Button variant="filled" onPress={deploy}>部署</Button>
<Button variant="outlined" size="small" onPress={cancel}>取消</Button>
<Button disabled>正在部署…</Button>

图标、纯图标与切换

按钮可以在标签旁边带一个图标:默认放在起始侧,把 iconPosition 设为 trailing 则放在末尾侧。图标一侧的内边距比标签一侧小一级,这样两侧看起来留白均匀。没有标签时,它就是纯图标按钮:一个宽度等于按钮高度的圆形,由 label 命名,此时 label 是必填的。设置 toggle 后,每按一次它都会切换自身的 selected,通过颜色和图标的填充来显示状态,并把这个状态作为“已按下”状态朗读出来。

纯图标按钮和切换按钮

searcheditfavorite下一步arrow_forward

在 Tanrei 中,图标按钮不是一个独立的组件。如果你熟悉 Material 的名称,它们的去向如下:

Material Tanrei
Icon button 有图标、没有标签的按钮
Standard icon button text 变体
Filled、tonal 和 outlined icon button filled、tonal 和 outlined 变体
Icon toggle button toggle 和 selected

请给 text 变体的切换按钮加上图标。它没有容器,如果只有标签,状态就只能靠文字颜色的变化来体现;有了图标的填充,状态才看得见。

纯图标按钮的宽度

纯图标按钮有三种宽度。无论取哪一种,高度都不变;带标签的按钮会忽略 width。

width 默认系统中的宽度(small / medium / large) 用于
default 宽度等于高度:32 / 40 / 56,呈圆形 大多数图标操作。
narrow 图标加上两侧各一小段留白:28 / 32 / 32,呈胶囊形 紧凑排列的一行图标操作,例如字段末尾的工具,或密集表格中的一行。
wide 40 / 52 / 72,呈胶囊形 一行之中应当比相邻操作更有分量的那一个操作。

纯图标按钮的宽度:default、narrow 和 wide

addaddaddmore_vertplay_arrow

narrow 按钮的宽度恰好等于图标加上两侧的留白,所以图标始终不会贴到胶囊的边缘;设计系统改变图标的尺寸时,按钮也随之变化。它画出来比最小目标窄,但点击区域并不窄:目标会延伸到可见边缘之外。请在按钮旁边为它留出空间,或者把 narrow 按钮放进工具栏或按钮组,它们会为其中的项安排间距。

忙碌,以及禁用但仍可到达

在按钮发起的操作进行期间,请设置 busy,而不是 disabled。忙碌的按钮保持它的尺寸、它在焦点顺序中的位置,以及用户的焦点。它在标签的位置画出加载指示器的圆弧,并拒绝每一次按下,所以提交按钮不会把表单提交两次;它也会拦住表单字段中的 Enter。按钮变为忙碌时,辅助技术会朗读一次 busy-label(“正在部署”)。操作结束时请说明发生了什么:那时按钮什么也不朗读。

禁用的按钮会退出焦点顺序,所以键盘或屏幕阅读器用户可能永远找不到它,也无从得知它为什么没有反应。如果用户能让这个操作变得可用,例如消息为空时的发送按钮,请加上 focusable-when-disabled:按钮画成禁用的样子,按禁用朗读,也不执行任何操作,但 Tab 仍然能到达它,于是工具提示或描述就可以说明原因。

忙碌,以及禁用但仍可到达

部署save发送

全宽

设置 full-width,按钮就和它的容器一样宽,例如窄表单、卡片或面板底部唯一的操作。它保持自己尺寸的高度、内边距和圆角,标签和图标居中。放不下的标签会换行,而不会被截断。当按钮组、工具栏或对话框、弹出框的操作区来排列按钮时,由它们决定宽度,按钮上的 full-width 不起作用:要让几个按钮分享一行,请给按钮组设置 full-width。

全宽按钮

创建账号upload上传文件

破坏性操作

如果按钮的操作会删除或丢弃一些可能无法找回的东西(删除、放弃、移除成员),就给它设置 destructive。按钮保持其变体的强调程度,并在其中采用错误色。filled 按钮用错误色填充,tonal 按钮用错误容器色填充;elevated、outlined 和 text 按钮用错误色绘制标签,outlined 按钮的边线也用这个颜色。禁用时,它与同一变体的任何禁用按钮看起来一样。

像为其他操作那样选择变体。当删除正是这个视图要完成的那一步时,例如“删除项目?”对话框中的确认操作,用 filled。当它只是若干操作之一时,例如列表每一行中的“移除”,用 outlined 或 text。纯图标按钮也可以设置 destructive,但对于无法撤销的操作,标签比图标更清楚。切换按钮没有破坏性外观。

链接也没有。带 href 的按钮会忽略 destructive,按其变体的链接来绘制,因为跟随链接不会删除任何东西:它只是把用户带到可以删除东西的地方。应当标记的是那个地方的操作,例如链接打开的页面中的“删除”按钮。调试构建会报告设置了 destructive 的链接。

各变体的破坏性按钮,以及纯图标按钮

删除项目放弃草稿移除成员清除历史记录delete

有目标地址的按钮是链接

给按钮指定 href,它就是链接,而不是按钮:控件还是同一个,在每种状态下都用同样的令牌绘制,但角色是链接,并且拥有平台赋予链接的一切。在 Web 上它是真正的锚元素,所以用户可以在新标签页中打开它、复制它的地址,并在跟随之前看到它通向哪里。只要激活控件的结果仅仅是把用户带到别处,就使用这种形态。不要在普通按钮的点击处理函数里进行导航。

链接:当前位置、新标签页、纯图标和禁用

按钮组工具栏apps按钮组
<tanrei-button href="/getting-started/">开始使用</tanrei-button>

<!-- 新标签页:标签后面的标记会自动绘制;target-label 是屏幕阅读器读出的内容。 -->
<tanrei-button variant="outlined" href="https://example.com/source" target="new" target-label="在新标签页中打开">
  查看源代码
</tanrei-button>

<!-- 仅限 Web:rel 和 download 会传给锚元素。 -->
<tanrei-button variant="tonal" href="/report.pdf" download="report.pdf"><span slot="icon">download</span>下载报告</tanrei-button>

<script type="module">
  import '@tanrei/web/components/button';
  // click 先触发。客户端路由可以取消它,自己接管导航。
  document.querySelector('[href="/getting-started/"]').addEventListener('click', (event) => {
    event.preventDefault();
    router.navigate('/getting-started/');
  });
</script>
import { Button } from '@tanrei/react';

<Button href="/getting-started/">开始使用</Button>

<Button variant="outlined" href="https://example.com/source" target="new" targetLabel="在新标签页中打开">
  查看源代码
</Button>

// click 先触发。客户端路由可以取消它,自己接管导航。
<Button
  href="/getting-started/"
  onClick={(event) => {
    event.preventDefault();
    router.navigate('/getting-started/');
  }}
>
  开始使用
</Button>
  • 按键。 Enter 跟随链接。Space 的作用与它在任何链接上的作用相同(在 Web 上是滚动页面),而不是它在按钮上的作用。
  • target。 same(默认)在用户当前所在的位置打开目标。new 打开新的标签页或窗口。这时带标签的按钮会在标签后面显示 open_in_new 图标,除非那里已有你自己放置的后置图标;同时 targetLabel 成为必填项:Tanrei 不自带任何文字,所以要由你用产品的语言写出“在新标签页中打开”。
  • 禁用。 禁用的链接按钮不能被跟随、复制或拖动,并作为禁用的链接暴露给辅助技术。
  • 切换。 切换按钮保存的是一个状态,所以不能有目标地址:设置了 toggle 时,href 会被忽略。
  • 仅在 Web 上,rel 和 download 会传给锚元素。target 设为 new 时,rel 中总会加上 noopener。
  • 没有“已访问”的外观。链接按钮的颜色与任何按钮一样,表达的是它的强调程度和状态。

在表单中

在 Web 上,按钮是它所属表单的控件之一,type 说明按下它会对表单做什么。其他平台没有表单元素:在那些平台上,你在按钮的按下和字段的 action 中调用同一个函数。

type 按下时
button(默认) 对表单什么也不做。表单里的按钮可以打开选择器或添加一行,而不提交任何内容。
submit 请求表单提交,与浏览器自己的提交按钮一样:先是 click,然后是表单的校验,最后是表单的 submit 事件。
reset 在 click 和表单的 reset 事件之后,把表单的每个控件恢复为初始值。
<form id="profile">
  <tanrei-text-field label="姓名" name="name" required></tanrei-text-field>
  <tanrei-button type="reset" variant="text">清除</tanrei-button>
  <tanrei-button type="submit" variant="outlined" name="intent" value="draft">保存草稿</tanrei-button>
  <tanrei-button type="submit" name="intent" value="publish">发布</tanrei-button>
</form>

<!-- 在表单之外:form 属性通过 id 指明表单。 -->
<tanrei-button type="submit" form="profile" name="intent" value="publish">发布</tanrei-button>

<script type="module">
  import '@tanrei/web/components/button';
  import '@tanrei/web/components/text-field';
  document.querySelector('#profile').addEventListener('submit', (event) => {
    event.preventDefault();
    // 被按下的按钮的 name 和 value 就在数据里:"draft" 或 "publish"。
    const data = new FormData(event.target, event.submitter);
    save(data.get('name'), data.get('intent'));
  });
</script>
import { Button, TextField } from '@tanrei/react';

<form
  id="profile"
  onSubmit={(event) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget, (event.nativeEvent as SubmitEvent).submitter);
    save(data.get('name'), data.get('intent'));
  }}
>
  <TextField label="姓名" name="name" required />
  <Button type="reset" variant="text">清除</Button>
  <Button type="submit" variant="outlined" name="intent" value="draft">保存草稿</Button>
  <Button type="submit" name="intent" value="publish">发布</Button>
</form>

// 在表单之外:form 是表单的 id。
<Button type="submit" form="profile" name="intent" value="publish">发布</Button>
  • 哪个表单。 包着按钮的那个表单,或者 form 属性通过 id 指明的那个表单;对话框的操作区或页头里的按钮,就是这样提交一个自己并不在其中的表单的。元素的 form 属性(property)返回这个表单,不能被设置。
  • 在字段中按 Enter。 表单的第一个提交按钮就是它的默认按钮,无论它是 Tanrei 的按钮还是浏览器自己的按钮。在表单的字段中按 Enter 就会按下它:在 Tanrei 的文本字段、数字字段、颜色字段、组合框、一次性验证码输入框或搜索中,在 Tanrei 的复选框、单选按钮或开关上,以及在浏览器自己的单行输入框中,都是如此。按钮会像任何一次按下那样触发 click,取消这个 click 就取消了提交。
  • 禁用。 禁用的提交按钮什么也不提交;当它是表单的默认按钮时,它还会拦住 Enter,所以尚未就绪的表单不会绕过它的按钮被提交。位于 <fieldset disabled> 之内的按钮随之禁用,无论它的 type 是什么。位于 <fieldset disabled> 之内的链接按钮则不会被禁用。
  • 按下的是哪个按钮。 只有当表单是由这个按钮提交时,才会发送它的 name 和 value。请在你的 submit 监听器里,从 event.submitter.value 或 new FormData(form, event.submitter) 读取它们。event.submitter 不是 Tanrei 元素,而是这个元素在 submit 事件派发期间放在自己位置上的一个原生 <button>,所以不要拿 event.submitter 与这个元素比较。
  • 表单如何提交完全由表单自己决定。formaction、formmethod、formenctype、formtarget 和 formnovalidate 不会被读取。要不经校验就保存草稿,请在那个按钮的 click 监听器里设置表单的 noValidate,这个监听器在校验之前运行。
  • 重复提交。 按钮不防范第二次按下。请在你的请求进行期间禁用它,或者忽略第二次 submit。
  • 切换按钮和链接会忽略 type:切换状态或前往别处的一次按下,不会提交表单。不属于这三者的 type,行为与 button 相同。

比标签更长的名称

当几个按钮上画的是同一个词时,例如列表每一行里的“移除”,仅凭这个词,屏幕阅读器用户无从知道某个按钮属于哪一行。给每个按钮一个 label:这时它就是按钮完整的无障碍名称,取代画出的文字,而按钮画出来的样子与原先完全相同。

每一行都是同一个词,每个按钮按所在的行命名

移除移除移除
label 画出的标签 无障碍名称
已给出 无(纯图标) label
为空 有 画出的文字
已给出 有 完整的 label,而且只有它
  • 名称必须包含画出的文字。 使用语音输入的用户会说出他们在按钮上看到的那个词。只要你的语言允许,就让名称以这个词开头:画着“移除”的按钮,名称写“移除 Inter”。字母大小写和多余的空格不影响。
  • 调试构建会报告不包含所画文字的 label。按钮仍然使用你给的名称。它绝不退回到画出的文字,也绝不把两者拼接起来,因为短语的语序属于你的语言。
  • 描述不是名称。 把一个按钮与相邻按钮区分开的,是它的名称。按下后会发生什么,例如“在新标签页中打开”,属于描述,在名称和角色之后朗读。

规格

尺寸与禁用

SmallMediumLargeDisabledDisabled

状态

variant 启用 禁用 悬停 聚焦 按下
filled filled, 启用 filled, 禁用 filled, 悬停 filled, 聚焦 filled, 按下
filled · toggle · selected filled toggle selected, 启用 filled toggle selected, 禁用 filled toggle selected, 悬停 filled toggle selected, 聚焦 filled toggle selected, 按下
tonal tonal, 启用 tonal, 禁用 tonal, 悬停 tonal, 聚焦 tonal, 按下
tonal · toggle · selected tonal toggle selected, 启用 tonal toggle selected, 禁用 tonal toggle selected, 悬停 tonal toggle selected, 聚焦 tonal toggle selected, 按下
elevated elevated, 启用 elevated, 禁用 elevated, 悬停 elevated, 聚焦 elevated, 按下
elevated · toggle · selected elevated toggle selected, 启用 elevated toggle selected, 禁用 elevated toggle selected, 悬停 elevated toggle selected, 聚焦 elevated toggle selected, 按下
outlined outlined, 启用 outlined, 禁用 outlined, 悬停 outlined, 聚焦 outlined, 按下
outlined · toggle · selected outlined toggle selected, 启用 outlined toggle selected, 禁用 outlined toggle selected, 悬停 outlined toggle selected, 聚焦 outlined toggle selected, 按下
text text, 启用 text, 禁用 text, 悬停 text, 聚焦 text, 按下
text · toggle · selected text toggle selected, 启用 text toggle selected, 禁用 text toggle selected, 悬停 text toggle selected, 聚焦 text toggle selected, 按下
每个单元格都是真实的组件,停留在该状态下,无法操作。 此处未绘制:loading。

构成

构成

绘制在 Web 包所绘的测试样例 filled-leading-icon 之上。
部件 说明
1 container The shape that holds the content, defines the visual bounds and carries hover, focus and press. A pill with a label; with an icon alone, as wide as it is high (a circle in the default system's corner) or at the narrow or the wide width of its size.
2 label · 可选 Text that names the action.
3 icon · 可选 A glyph of the system's icon set, at the leading or the trailing side of the label, or alone. Outlined when unselected, filled when selected.
4 outline · 可选 The border of the outlined variant.
5 focus-ring Gap-then-ring indicator shown for keyboard focus.

尺寸

尺寸

长度 令牌 值(px)
container padding-inline-start-with-icon tanrei.comp.button.container.padding-inline-start-with-icon.medium 12
container gap tanrei.comp.button.container.gap.medium 8
container padding-inline-end tanrei.comp.button.container.padding-inline-end.medium 16
container height tanrei.comp.button.container.height.medium 40
icon size tanrei.comp.button.icon.size.medium 24
绘制在 Web 包所绘的测试样例 filled-leading-icon 之上。

属性

属性 类型 默认值 说明
variant filled | tonal | elevated | outlined | text "filled" Emphasis level; whether the action deletes or discards something is destructive, which every variant takes. Use one filled button per view for the main action; an icon-only text button is the quiet icon action of a toolbar or a row. A design system may add values. Default filled; a Button placed as a dialog's or a side sheet's action without a variant takes that host's (DLG-20, SSH-17), and a side sheet's close button takes comp.side-sheet.close-button.variant (SSH-5).
destructive boolean false Marks an action that deletes or discards something a person may not get back (Delete, Discard, Remove member): the button keeps its variant's emphasis and takes the error role's color in it — filled in the error color, tonal in its tonal container, and the error color for the label of an elevated, outlined or text button and the edge of an outlined one (BTN-40). Use it for that kind of action alone, never for emphasis. It adds a color, not a meaning: the label still says what the button does (BTN-42). A toggle has no destructive look: ignored while toggle is true (BTN-41). A link deletes nothing: ignored while href is not empty (BTN-43).
size small | medium | large "medium" Visual height (32 / 40 / 56), which is also an icon-only button's width at its default width. The hit area always meets the minimum target. A design system may add values.
width default | narrow | wide "default" How wide an icon-only button is. default: as wide as it is high (32 / 40 / 56). narrow: its glyph with a small inset on each side (28 / 32 / 32), for a tight row of icon actions. wide: 40 / 52 / 72, for the one action of a row that should weigh more. Ignored when the button has a label, and while fullWidth is true. The hit area meets the minimum target at every width.
fullWidth boolean false Makes the button take the whole width its container gives it, its content centred, at the height of its size (BTN-39): a form's only action on a narrow screen, the action at the foot of a card or a sheet. Ignored while a host lays the button out (a button group, a toolbar, or the actions of a dialog, a side sheet, a popover, a callout or an empty state), which gives it a width of its own (a button group's fullWidth for a row of them).
iconPosition leading | trailing "leading" Which side of the label the icon sits on, in the reading direction. Ignored when the button has no label or no icon.
toggle boolean false Makes the button a two-state control: activation switches selected, and assistive technology announces it as pressed or not pressed. A toggle takes no destination: href is ignored while toggle is true.
selected boolean false Whether a toggle is on. Ignored unless toggle is true.
disabled boolean false Prevents interaction and removes the button from the focus order — unless focusableWhenDisabled keeps it there (BTN-38). A disabled button with a destination is not a link that can be followed (BTN-25); on the web a disabled submit button sends nothing, and still stops Enter from sending its form where it is the form's default button (BTN-32).
focusableWhenDisabled boolean false Keeps a disabled button in the focus order: it is drawn and exposed as disabled and refuses every activation, but a keyboard and a screen reader can still reach it, so that a person can learn why it does nothing (a tooltip, a description). For an action a person can make available — a send button while the message is empty. Ignored unless disabled is true (BTN-38).
busy boolean false The action it started is under way: the button keeps its focus, its place in the focus order and its size, draws the loading indicator's arc in place of its label and icon, refuses every activation, and tells assistive technology through busyLabel (BTN-36, BTN-37). For a save or a send that takes a moment; it is not disabled, so focus stays where the person left it.
busyLabel string "" What assistive technology is told while the button is busy ("Saving"): announced once when it becomes busy and read after its name while it is. Tanrei ships no words of its own. Required while busy is true; ignored otherwise.
href string "" The destination: 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 button 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 button it would otherwise be (BTN-22, BTN-23). Ignored while toggle is true: a control that holds a state is not a destination (BTN-26).
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 a labelled button marks with the glyph open_in_new after its label and says through targetLabel (BTN-24). 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 accessible name, where it is more than what the button draws. Required for an icon-only button: an icon alone is not a name. Optional for a button that draws a label: left empty, the drawn text is the name; given, it is the whole name in place of the drawn text, which stays drawn — for an action repeated in every row (drawn "Reset", named "Reset: Spacing") — and it must contain the drawn text, so that a person who says what they see is heard (BTN-16). It is about this one button, so it is the instance's alone and reads no word of the product's.

插槽

插槽 说明
default Label content: the text that names the action. Leave it empty for an icon-only button.
icon The icon: a glyph of the system's icon set, drawn through the platform's icon helper. With a label it sits at the side iconPosition names; without one it is the button's only content.

事件

事件 说明
click The button was activated by pointer, touch, keyboard or assistive technology. A link button emits it before its destination opens; a product that takes the navigation itself (a client-side router) cancels it (BTN-23).
change A toggle was activated; carries the new value of selected.

状态

行为

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

ID 需求
BTN-1 WHEN the user activates the button by pointer, touch, Enter or Space — a link button (BTN-22) by pointer, touch or Enter — THE button SHALL emit click exactly once.
BTN-2 WHILE disabled is true THE button SHALL NOT emit click or change, SHALL NOT be focusable and SHALL expose the disabled state to assistive technology.
BTN-3 WHILE hovered, keyboard-focused or pressed THE button SHALL render its container, label, icon, outline, 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.
BTN-4 WHEN the button receives keyboard focus THE button SHALL show the focus ring without animation; focus gained by pointer or touch SHALL NOT show it.
BTN-5 WHERE the visual size is below the minimum target THE button 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.
BTN-6 WHILE disabled is true THE button SHALL render its container, label, icon, outline, elevation and opacity from the disabled component tokens of its variant and, for a toggle, of its selection, and, while destructive is true, of its destructive parts (BTN-40).
BTN-7 WHEN variant or size names a value that the active design system does not have THE button SHALL render with the default value's tokens, and in debug builds report the unknown value.
BTN-8 WHEN the button is pressed by pointer, touch, Enter or Space THE button 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.
BTN-9 WHILE pressed THE button 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; label, icon, outline, 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.button.motion.feedback-duration with motion.state-easing, fades over the motion.state-duration of its size with motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced.
BTN-10 THE button 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. WHILE the button is an item of a connected button group THE button SHALL draw its corners and minimum width and height as the group sets them, the padding the group adds at a rounded outer end, and neither outline nor elevation ring along an edge the group joins to a neighbour under a divider, and, from a dialog, a side sheet or an image preview, its variant while none is set (DLG-20, SSH-17, IMP-8) and a host's close-button variant — the popover's, the side sheet's, the bottom sheet's, the callout's and the snackbar's close button, each an icon-only Button of the small size (POP-13, SSH-5, BSH-29, CLO-10, SNK-34; the image preview's bar names one variant for its close and zoom controls, IMP-8), and, from any component that draws it on a colored surface of its own, a tone (BTN-45), through an internal channel that is not part of its public surface (BGR-2, BGR-3, BGR-16, BGR-22, BGR-23).
BTN-11 WHILE the button has both a label and an icon THE button SHALL draw the icon at the side iconPosition names in the reading direction (leading at the start, trailing at the end, both mirrored in right-to-left layouts) and the label comp.button.container.gap.medium from it at the medium size (and the gap of its own size otherwise), with comp.button.container.padding-inline-start-with-icon.medium on the icon's side for a leading icon and comp.button.container.padding-inline-end-with-icon.medium for a trailing one, and the plain padding (comp.button.container.padding-inline-start.medium, comp.button.container.padding-inline-end.medium) on the other side, each of its own size; WHILE it has a label and no icon it SHALL use the plain padding on both sides and ignore iconPosition.
BTN-12 WHILE the button has an icon and no label THE button SHALL draw its container at the height of its size — comp.button.container.height.medium at the medium size, and the height of its own size otherwise — and at the width its width names: as wide as it is high WHILE width is default, as wide as its glyph's box (comp.button.icon.size-without-label.medium) with comp.button.container.padding-inline-narrow.medium on each side of it WHILE width is narrow, and comp.button.container.width-wide.medium wide WHILE width is wide (each of its own size), with the glyph centred, and SHALL round it by comp.button.container.shape, capped at half its shorter side, so that the default system's sys.shape.corner.full draws a circle at the default width and a capsule at the other two, and a smaller corner a rounded square or rectangle. The narrow width SHALL be the glyph and that inset twice, and nothing more: the button has no narrow width of its own to disagree with them, so a narrow button follows its glyph wherever a design system re-sizes it, and its container, state layer, ripple and outline are a capsule of that width. In the default system the inset is sys.space.space50 at every size, the least room that keeps the glyph clear of the capsule's edge; a design system that wants its narrow buttons to differ more by size gives a size a larger inset. It SHALL NOT draw an icon-only button at any other width, except to the minimum width a connected button group sets and the padding that group adds at an outer end (BTN-10). WHERE text size scales the glyph (every size follows the root font size on the web) the container SHALL grow with it and keep the proportion of its width to its height.
BTN-13 THE button SHALL draw its icon at the icon box of its size — comp.button.icon.size.medium beside a label and comp.button.icon.size-without-label.medium alone at the medium size, and so on — with the family and fill of its selection (sys.icon.family.selected and sys.icon.fill.selected only when toggle and selected are both true; otherwise sys.icon.family.unselected and sys.icon.fill.unselected), sys.icon.weight, and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max; beside a label the icon SHALL take the label color of the button's look, and alone the icon color of its look (comp.button.text.icon.color for a plain text button, comp.button.text.selected.icon.color for a text toggle that is on, and so on).
BTN-14 WHEN toggle is true and the user activates the button THE button SHALL emit click and then change with the opposite of selected, in that order (as a native checkbox does, and as CHP-11 says for a filter chip), and SHALL expose selected to assistive technology as pressed or not pressed; WHILE toggle is false THE button SHALL NOT expose a pressed state.
BTN-15 WHILE toggle is true THE button SHALL render its container, label, icon and outline from the selected parts of its variant when selected is true (comp.button.filled.selected.container.color, and so on) and from the unselected parts when it is false (comp.button.filled.unselected.container.color, and so on), and its elevation from its variant's own comp.button.filled.container.elevation (and so on); WHILE toggle is false it SHALL render from the variant's own parts.
BTN-16 WHILE the label prop is not empty THE button SHALL expose it as its accessible name, whole and alone, whether or not it draws a label; WHILE the label prop is empty and the button draws a label THE button SHALL expose the drawn label's text as its accessible name; WHILE the label prop is empty and the product gives the button a name of its own on the element — aria-label, or aria-labelledby naming an element — THE button SHALL expose that name as its accessible name, over the drawn label's text where it draws one, because the platform's text alternative computation puts an author's name over the element's content; WHILE the label prop is not empty THE label prop SHALL be the name, over the product's. WHILE it has an icon and no drawn label THE button SHALL keep the glyph from assistive technology, and WHILE the label prop is empty as well and the product gives it no name of its own SHALL still render, and in debug builds report that it has no accessible name. WHILE the button draws a label and the label prop is not empty THE button SHALL draw the label exactly as it would without the prop, SHALL NOT expose the drawn text a second time beside the name, and in debug builds SHALL report a label prop that does not contain the drawn text — the two compared with white space at their ends dropped, runs of white space read as one space and letters compared without case — because a person who speaks the words they see must be heard by the control that shows them. That report SHALL be all the button does about it: it SHALL still expose the label prop, whole and alone, as the name, SHALL NOT fall back to the drawn text and SHALL NOT add the drawn text to the name, in a debug build as in any other. THE button SHALL NOT make a name of the two itself, by joining them or in any other way: the order of a phrase is the language's, and the whole name is the product's to give. A description — the platform's own, or targetLabel (BTN-24) — SHALL be read after the name and the role and SHALL NOT be exposed as a name or as part of one.
BTN-17 IF the button has neither a label nor an icon THE button SHALL still render its container at the height of its size and as wide as it is high, and in debug builds report that it has no content.
BTN-18 THE button 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 button is shown THE button SHALL draw itself for the new direction at once, without a motion of its own, keeping its selection and its focus.
BTN-19 WHERE the layout direction is right-to-left THE button 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 BTN-11 gives. THE button 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.
BTN-20 THE button 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 button SHALL still place the label and align its lines by the layout direction, not by the text's own.
BTN-21 WHILE the button has a label THE button SHALL ignore width: a labelled button is as wide as its content and its paddings (BTN-11). WHEN width names a value the button does not define THE button SHALL render at the default width, and in debug builds report the unknown value. THE button SHALL meet the minimum target of BTN-5 at every width by extending its hit area: a narrow button SHALL NOT be drawn wider to reach it.
BTN-22 WHILE href is not empty and toggle is false THE button SHALL be a link and not a button: it SHALL expose the link role, named as BTN-16 names a button, 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 button's to draw or to take over; and SHALL draw its container, label, icon, outline, elevation and opacity in every state from the same tokens as a button of its variant, size and width that has no destination, with no look of its own for a link and none for a destination already visited.
BTN-23 WHEN the user activates a link button by pointer, touch or Enter THE button 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 BTN-8 and BTN-9 say and SHALL NOT hold the navigation back for sys.motion.duration.press-hold. THE button SHALL NOT activate a link button 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 button SHALL leave them to the platform.
BTN-24 WHILE the button is a link and target is new THE button 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, WHILE it has a label and no icon at its trailing side, SHALL draw the glyph open_in_new of the system's icon set after the label as BTN-11 draws a trailing icon: comp.button.container.gap.medium from the label, at comp.button.icon.size.medium, with comp.button.container.padding-inline-end-with-icon.medium between it and the end edge at the medium size (and each of its own size otherwise), a leading icon keeping its own side as BTN-11 says; in the label color of its look and state; 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 the button holds the product's own icon at its trailing side, or has no label, THE button SHALL draw no mark of its own: the product has chosen what that place shows. Whether a mark is drawn or not THE button 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 button SHALL draw no mark and SHALL ignore targetLabel.
BTN-25 WHILE href is not empty and disabled is true THE button SHALL NOT be a link that can be followed: it SHALL render as BTN-6 says, the mark of BTN-24 in the disabled label 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).
BTN-26 WHILE toggle is true THE button SHALL ignore href, target and targetLabel and stay the toggle BTN-14 describes, and in debug builds SHALL report that a toggle cannot be a link: a control that holds a state is not a destination. WHEN target names a value the button does not define THE button 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 button SHALL be a button without a destination, and in the last two cases report it in debug builds.
BTN-27 WHERE the platform has a form element (the web) and WHILE type is submit, WHEN the user activates the button by any means of BTN-1 and the product has not cancelled its click THE button SHALL ask its form (BTN-29) to submit as the platform's own submit button does: after click has been emitted, exactly once for one activation, with the form's own validation run first and its failure shown by the platform and by the fields as they show it, then the form's submit event, which the product may cancel, and with the pressed button named in what is sent (BTN-31). It SHALL show the pressed state as BTN-8 and BTN-9 say and SHALL NOT hold the submission back for sys.motion.duration.press-hold. WHILE type is button, which is the default, THE button SHALL do nothing to any form, whatever it stands in.
BTN-28 WHERE the platform has a form element (the web) and WHILE type is reset, WHEN the user activates the button and the product has not cancelled its click THE button SHALL ask its form (BTN-29) to reset as the platform's own reset button does: after click has been emitted, the form's reset event, which the product may cancel, and then every control of the form back at its initial value; it SHALL validate nothing, submit nothing and add nothing to any later submission.
BTN-29 THE button's form SHALL be the one the platform gives a control at its place: the nearest form among its ancestors, or, WHILE the web's form attribute is not empty, the form of that id in the same document, and then not an ancestor; it SHALL be read when the button is activated, so that a button moved into a form, or a form attribute written later, is followed. WHERE the platform lists a form's controls (the web's form.elements) THE button SHALL be listed among them at its place in document order, whatever its type and also WHILE type is button — a link button too, since the element is one kind of control — and being listed SHALL send nothing: a Button adds to a submission only what BTN-31 says. IF type is submit or reset and the button has no form WHEN it is activated THE button SHALL emit click and do nothing more, and in debug builds report that a submit or a reset button has no form.
BTN-30 WHERE the platform has a form element (the web) THE default button of a form SHALL be the first submit button among the form's controls in document order, a Button whose type is submit counted exactly as the platform's own submit button is. WHEN the person presses Enter in a field of the form that submits it implicitly — the platform's own single-line inputs; Tanrei's text field (TXI-3), number field (NMI-20), color field (CLI-19), combobox (CMB-28), one-time-code input (OTC-13) and search (SRC-13), once their own event for that key has not been cancelled; and Tanrei's checkbox, radio button and switch, which emit nothing for the key (CHK-1, RAD-1, SWT-1) — and the default button is a Button, THE button SHALL act as if it had been activated: click emitted exactly once, and then, unless the product cancelled it, the submission of BTN-27 with that button named in it (BTN-31); it SHALL NOT show the pressed state for a key pressed elsewhere and SHALL NOT take focus. WHILE the default button is disabled — a Button or the platform's own — THE form SHALL NOT be submitted by Enter in any of those controls, as the platform's own disabled default button prevents it from the platform's own inputs. WHILE the default button is the platform's own, Enter in a Tanrei control SHALL press it as Enter in the platform's own input does: its click, then the submission with its name and value. WHILE the form has no submit button of either kind THE button has no part in it: the field and the platform do as they do without one. THE button SHALL draw a form's default button no differently from any other: its emphasis is its variant's.
BTN-31 WHEN a Button submits its form (BTN-27, BTN-30) THE submission SHALL carry the pair of that button's name and value — once, at most, and only WHILE its name is not empty; an empty value is sent as an empty value — and SHALL carry nothing of any other Button; WHEN the form is submitted by other means (another button, a script's own request) it SHALL carry nothing of this one. THE submit event SHALL name as its submitter a submit button of the platform's own that holds the same name and value and lasts no longer than the event's dispatch, because the platform lets only its own buttons be a submitter: a product reads which button was pressed from that name and value, or from click, and SHALL NOT expect the submitter to be the Tanrei element. THE button SHALL NOT override where or how its form is sent: the address, the method, the encoding, the target and whether the form is validated are the form's alone.
BTN-32 WHILE disabled is true THE button SHALL submit and reset nothing by any means — a press, a key, assistive technology or a script's click — SHALL add nothing to a submission, and SHALL still be counted as its form's default button where it is the first submit button (BTN-30), so that a form whose button is disabled is not sent by Enter behind it. WHERE the platform has a form element (the web), WHILE the button is not a link and stands in a group of controls the page has disabled as a whole (a disabled fieldset) THE button SHALL be disabled exactly as BTN-2 and BTN-6 say, whatever its type, and SHALL be enabled again at once when the group is; a link button SHALL NOT be disabled by it, as no link of the platform is.
BTN-33 WHILE toggle is true THE button SHALL ignore a type of submit or reset and stay the toggle BTN-14 describes, and in debug builds SHALL report that a toggle cannot submit or reset a form: a press that switches a state is not one that sends it. WHILE the button is a link (BTN-22) THE button SHALL ignore a type of submit or reset and stay the link, and in debug builds SHALL report that a link cannot submit or reset a form: it goes somewhere, and what a form sends on the way is the form's own action. WHEN type names a value the button does not define THE button SHALL behave as button and submit nothing — where the platform's own button would submit for an unknown type — and in debug builds report the unknown value. WHILE type is button THE button SHALL ignore the web's form, name and value without a report: they do no harm on a button that sends nothing.
BTN-34 WHERE the platform has no form element (SwiftUI, Compose, Flutter, React Native) THE button SHALL have no type, no form and no name and value to send: there a product calls one function of its own from the button's click and from a field's action (TXI-3), and nothing of Tanrei's joins the two or decides which button a key in a field presses.
BTN-36 WHILE busy is true THE button SHALL draw its container, outline, elevation and opacity from its enabled tokens (of its variant and, for a toggle, of its selection, or of its destructive parts, BTN-40), SHALL keep its label and icon in place without drawing them so that its width and height do not change, and SHALL draw at the centre of the container the loading indicator's arc (LDG-1; under reduced motion LDG-4's whole circle, its opacity easing once per cycle) in a box of the button's icon size (icon.size-without-label where it is icon-only), comp.button.busy.indicator.thickness thick, in the label color of its variant (the icon color where it is icon-only), turning once per comp.button.motion.busy-cycle-duration. It SHALL show neither the hovered nor the pressed state, and SHALL show the focused one. WHEN busy becomes false THE button SHALL draw its label and icon again at once, without a motion.
BTN-37 WHILE busy is true THE button SHALL keep its focus and its place in the focus order, SHALL NOT emit click or change, and SHALL submit, reset and open nothing by any means (a press, a key, assistive technology or a script's click); where it is its form's default button it SHALL stop Enter in a field of the form from submitting it, as a disabled default button does (BTN-30, BTN-32), so that a form is not sent twice. It SHALL expose to assistive technology that it cannot be activated now (on the web aria-disabled) and busyLabel as its description, read after its name, and WHEN busy becomes true SHALL have busyLabel announced once, politely, without moving focus; nothing SHALL be announced when it ends, because what the action did is the product's to say. WHILE busy is true and busyLabel is empty THE button SHALL still render and in debug builds report that a busy button does not say so.
BTN-38 WHILE disabled is true and focusableWhenDisabled is true THE button SHALL be drawn as BTN-6 says and refuse every activation as BTN-2, BTN-25 and BTN-32 say, but SHALL stay in the focus order, take focus from the keyboard and from a pointer, keep focus when it becomes disabled, show its focus indicator, and expose the disabled state to assistive technology while staying focusable (on the web aria-disabled, not the platform's disabled state), so that a person can reach it and learn why it does nothing. It SHALL show neither the hovered nor the pressed state. A link button so disabled is the disabled link of BTN-25, reachable by Tab. WHERE the platform disables a group of controls as a whole (a disabled fieldset on the web) THE button SHALL leave the focus order with the group, whatever focusableWhenDisabled says, as the platform's own controls do. WHILE focusableWhenDisabled is false, disabled keeps its meaning (BTN-2).
BTN-39 WHILE fullWidth is true and no host lays the button out THE button SHALL take the whole inline size its container gives it, and SHALL keep the height, the paddings, the corners, the outline and the minimum target of its size, variant and state, centring its content in that width — the label and the icon as one row, in the order and at the gap BTN-11 gives them, or an icon-only button's glyph, whatever width says. WHERE that width is narrower than the label THE button SHALL wrap the label, its lines centred, rather than truncate it or draw past its container. WHILE a host lays the button out (a button group, a toolbar, or the actions of a dialog, a side sheet, a popover, a callout or an empty state) THE button SHALL take the width that host gives it and ignore fullWidth: the host's own full width (BGR-17) is how a row of buttons shares one width.
BTN-40 WHILE destructive is true and toggle is false THE button SHALL render its container, label, icon and outline from the destructive parts of its variant (comp.button.filled.destructive.container.color, and so on for each variant), in each state from the destructive parts of that state, and its elevation, shape and opacity as it would without destructive; WHILE destructive is false it SHALL render from the variant's own parts. In every shipped system the destructive parts are the error color role at the variant's emphasis — filled: the error color with the on-error color on it; tonal: the error container with the on-error-container color on it; elevated, outlined and text: the variant's own container with the label and the icon in the error color, and an outlined button's edge in it too — and disabled they are those of the variant's plain disabled look, so that a disabled destructive button looks like any disabled button of its variant. An icon-only button takes destructive as a labelled button does; a link button ignores it (BTN-43).
BTN-41 WHILE toggle is true THE button SHALL ignore destructive and render as BTN-15 says, and in debug builds SHALL report that a toggle has no destructive look: a control that switches a state on and off does not delete anything by being pressed.
BTN-42 THE button SHALL NOT make color the only sign that its action is destructive: WHILE destructive is true it SHALL keep its role, its accessible name, its states and its keyboard behaviour exactly as they are without it, and SHALL expose nothing more to assistive technology on the web, where no role or state says it; WHERE the platform has a destructive role of its own (SwiftUI's ButtonRole.destructive) THE button SHALL be given that role. Its label, or its label prop on an icon-only button, says what it destroys.
BTN-43 WHILE the button is a link (BTN-22) THE button SHALL ignore destructive and render as the link button of its variant without it, in each state and on a tone a host gives it (BTN-45), and in debug builds SHALL report that a link only leads somewhere and does not itself delete anything: the action that deletes is the one at its destination, and that action carries destructive.
BTN-45 WHERE the component that places the button gives it a tone through the internal channel (BTN-10) — the content color of the colored surface it draws the button on (a status callout's, an inverse or status snackbar's), and optionally the color a focus indicator's outer stroke takes on that surface — THE button SHALL draw its plain look in that tone instead of its variant's own colors, at rest and in each state: its label, its icon and its state layer in the tone's color; its container, where its variant draws one at rest (filled, tonal, elevated, and a variant a layer adds), in the tone's color at comp.button.tone.container.opacity, and where its variant draws none (outlined, text) as its variant's own, an outlined button's edge taking the tone's color at that opacity instead; hovered, focused and pressed, that resting container with the tone's color laid over it at the system's state-layer opacity of the state (sys.state.hover-state-layer-opacity, sys.state.focus-state-layer-opacity, sys.state.pressed-state-layer-opacity), as the core rules lay a state over a resting color; disabled, the container (where its variant draws one) in the tone's color at sys.state.disabled-container-opacity and the label and icon at sys.state.disabled-content-opacity; and the outer stroke of its focus indicator in the tone's focus color, or its color where none is given. Its size, shape, paddings, elevation, opacity and answer to a press SHALL be its own, and a toggle and a destructive button SHALL keep their own looks (BTN-15, BTN-40). WHILE no tone is given THE button SHALL be drawn as without this clause. A tone is not part of the button's public surface: no prop, attribute or styling property gives one.
BTN-46 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 button 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 button is invalid — except those the button 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 button SHALL take it off that element. WHEN the product sets an aria- attribute the button owns THE button SHALL report it in debug builds.

各平台名称对照

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

规格 Web SwiftUI Jetpack Compose Flutter React Native
组件 tanrei-button TanreiButton TanreiButton TanreiButton Button
variant · 属性 variant variant variant variant variant
destructive · 属性 destructive role destructive isDestructiveAction destructive
size · 属性 size size size size size
width · 属性 width width width width width
fullWidth · 属性 fullWidth fullWidth fullWidth fullWidth fullWidth
iconPosition · 属性 iconPosition iconPosition iconPosition iconAlignment iconPosition
toggle · 属性 toggle isSelected onCheckedChange isSelected toggle
selected · 属性 selected isSelected checked isSelected selected
disabled · 属性 disabled disabled enabled onPressed disabled
focusableWhenDisabled · 属性 focusableWhenDisabled focusableWhenDisabled focusableWhenDisabled focusableWhenDisabled focusableWhenDisabled
busy · 属性 busy busy busy busy busy
busyLabel · 属性 busyLabel busyLabel busyLabel busySemanticLabel busyLabel
href · 属性 href destination href href href
target · 属性 target target target target target
targetLabel · 属性 targetLabel targetLabel targetLabel targetSemanticLabel targetLabel
label · 属性 label label contentDescription semanticLabel accessibilityLabel
click · 事件 click action onClick onPressed onPress
change · 事件 change isSelected onCheckedChange onPressed onSelectedChange
备注 The label is the default slot and the icon the icon slot (a glyph of the icon font); an icon-only button is one whose default slot is empty. iconPosition is reflected as the icon-position attribute. label sets aria-label on the inner <button> (or the anchor) whenever it is not empty; with a label in the default slot the slot's text stays drawn and the attribute replaces it as the name (BTN-16), and a debug build compares the two. A toggle sets aria-pressed on the inner <button>, switches its own selected and fires change, as a native checkbox does. width is the width attribute (default, narrow, wide); it does nothing while the default slot holds a label. With href the shadow root holds an <a part="container"> in place of the <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. In a form: a form-associated custom element (static formAssociated = true), as the text field is, for its form owner and for a disabled fieldset (formDisabledCallback through the shared primitive; not while it is a link). Web attributes, not spec props, as a checkbox's name and value are: type (button, submit, reset; default button, reflected; anything else is button and reported), form (the id of a form, the platform's own attribute for a form-associated element), name and value. The element has a read-only form property. A press of a submit button, after its click has gone through the page uncancelled, appends a hidden native <button type="submit"> with the same name and value to itself — a child in a slot it does not have, so it is drawn nowhere, naming the same form where the button names one by its form attribute — calls form.requestSubmit() with it and removes it before returning, so the form's validation, its submit event, event.submitter and new FormData(form, event.submitter) are the platform's, and the pair is sent at the button's own place among the form's entries, as the platform's own button's is; a reset button calls form.reset(). The form's default button is found by one primitive that walks form.elements in order and takes the first native submit button or <tanrei-button type="submit">: the text field, the number field, the color field, the combobox, the one-time-code input and the search call it in place of form.requestSubmit() after their action (or submit) event, and the selection primitive of the checkbox, the radio button and the switch calls it for Enter, and for Enter in a control of the platform's own, which knows only the platform's buttons, the browser itself is left to decide: one keydown listener per tree, in the capture phase (on the window for a document, on the shadow root for a form inside one), added while a form of that tree holds a Tanrei submit button, answers Enter going down in a native control of such a form — inside it, or naming it by the form attribute from outside — whose default button is Tanrei's by appending to that button, for the length of the key, a hidden native <button type="submit"> in a slot the button does not have: the form's first submit button exactly when the Tanrei button is, disabled while it is, and never the default button to the primitive. The browser presses it exactly where, when and whether it would press a default button of its own — after the field has committed what was typed (change, then click, then submit), for the controls and the modifier keys this engine submits on and no others, never for the Enter an input method or a suggestion list took, and not at all while it is disabled, in a form of one field too. That press is stopped at the window before a listener of the page hears it (at the shadow root, for a form in a closed one) and its own submission cancelled; the hidden button is removed, and the Tanrei button's click() is called, which emits its click and submits as any press of it does. A hidden button the browser did not press is removed at the key's keyup, and one is appended again for each repeat of a key held down. A capture listener a page added to the window before the form's first Tanrei submit button was connected hears the stopped press, as a click on a hidden button inside the Tanrei button. formaction, formmethod, formenctype, formtarget and formnovalidate are not read. The inner <button> stays type="button": it is in a shadow root, where no form can own it. focusableWhenDisabled is the focusable-when-disabled attribute and busyLabel the busy-label attribute. A disabled button that is focusable-when-disabled renders its native <button> without the disabled attribute and with aria-disabled="true", and refuses the press in a listener of its own; the host's disabled attribute is written as for any disabled button, and a disabled fieldset still takes it out of the focus order (BTN-38). A busy button renders aria-disabled="true" on its native <button> (or anchor), keeps its label and icon laid out under visibility: hidden, draws the arc as an inline SVG circle, and announces busy-label through a visually hidden role="status" region emptied and set again each time it becomes busy (BTN-37). fullWidth is the full-width attribute: the host is a block-level flex box as wide as its container and the native <button> grows to fill it; while a host lays it out (a button group, a toolbar, the actions of a dialog, a popover, a callout, an empty state, a file picker) the element carries the custom state placed (:state(placed)), under which full-width does nothing (BTN-39). destructive is a boolean attribute; it draws, and exposes nothing more (BTN-42), and on a link it draws nothing (BTN-43). The title is the label and the icon view builder the icon; an icon-only button is the initializer that takes label: and an icon and no title. A toggle is the initializer that takes isSelected: Binding<Bool>. disabled follows SwiftUI's .disabled(_:) environment. width: TanreiButtonWidth = .default is a parameter of the icon-only initializer alone, so a labelled button cannot be given one. Not built on this platform yet: the initializer that takes destination: URL in place of action: (there is none that takes both a destination and isSelected, so a toggle cannot be given one) 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 initializers that take a title take label: String? = nil as well, set with .accessibilityLabel over the title's text. SwiftUI has no form element that sends values, so there is no type (BTN-34): an app that wants Return to press a view's main button gives that TanreiButton .keyboardShortcut(.defaultAction), or calls the same function from a text field's action. Not built on this platform yet: busy and busyLabel (BTN-36, BTN-37), and focusableWhenDisabled (BTN-38). Not built on this platform yet: fullWidth (BTN-39). Not built on this platform yet: role: ButtonRole? = nil on every initializer, as SwiftUI's own Button(role:) takes it: .destructive is destructive, and any other role draws as nil does; the role is passed on to the system, so a destructive Button in a menu, a swipe action or a confirmation dialog is treated as SwiftUI treats its own (BTN-40 to BTN-43). Compose idiom is the inverse flag enabled. A non-null onCheckedChange makes it a toggle (as IconToggleButton), with checked its state; an icon-only button is the overload with an icon and a contentDescription and no text. width: TanreiButtonWidth = TanreiButtonWidth.Default is a parameter of the icon-only overload alone. Not built on this platform yet: the overload that takes href: String and no onCheckedChange 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 overloads that take text take contentDescription: String? = null as well; given, the node's semantics are cleared and set again with it, because a merged node would otherwise read the text and the description both. Compose has no form element, so there is no type (BTN-34): an app calls the same function from onClick and from a text field's action. Not built on this platform yet: busy and busyLabel (BTN-36, BTN-37), and focusableWhenDisabled (BTN-38). Not built on this platform yet: fullWidth (BTN-39). Not built on this platform yet: destructive: Boolean = false on every overload; Compose has no destructive role, so it draws and adds no semantics (BTN-40 to BTN-43). Flutter idiom: a null onPressed disables the button; iconAlignment (start / end) is Flutter's own name for the icon's side. A non-null isSelected makes it a toggle and onPressed flips it; an icon-only button is the TanreiButton.icon constructor, whose semanticLabel is required. width (TanreiButtonWidth.normal, .narrow, .wide; default is a reserved word in Dart) is a parameter of TanreiButton.icon alone. Not built on this platform yet: the TanreiButton.link and TanreiButton.iconLink constructors (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 constructors that take a child label take semanticLabel as well, optional there, applied with the child's own semantics excluded. Flutter's Form validates and saves and sends nothing, and has no default button, so there is no type (BTN-34): an app calls the same function from onPressed and from a text field's action. Not built on this platform yet: busy and busySemanticLabel (BTN-36, BTN-37), and focusableWhenDisabled (BTN-38). Not built on this platform yet: fullWidth (BTN-39). Not built on this platform yet: isDestructiveAction (bool, default false), the name Flutter's own CupertinoDialogAction gives the same flag; it draws and adds no semantics (BTN-40 to BTN-43). The label is children and the icon the icon prop; an icon-only button has no children and requires accessibilityLabel. A toggle has accessibilityState.selected and the togglebutton role where React Native offers it. width ("default" | "narrow" | "wide") is the icon-only width, not a layout width; it is ignored with children. Not built on this platform yet: a Button 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: accessibilityLabel is read with children as well as without, and replaces their text as the name. React Native has no form element, so there is no type (BTN-34): an app calls the same function from onPress and from a text field's action. Not built on this platform yet: busy and busyLabel (BTN-36, BTN-37), and focusableWhenDisabled (BTN-38). Not built on this platform yet: fullWidth (BTN-39). Not built on this platform yet: destructive (boolean, default false), the word React Native's own Alert button style uses; it draws and adds no accessibility props (BTN-40 to BTN-43).

令牌

令牌 引用 解析值(浅色)
--tanrei-comp-button-busy-indicator-thickness comp.loading-indicator.indicator.thickness.small 2px
--tanrei-comp-button-container-gap-large sys.space.space100 8px
--tanrei-comp-button-container-gap-medium sys.space.space100 8px
--tanrei-comp-button-container-gap-small sys.space.space100 8px
--tanrei-comp-button-container-height-large — 56px
--tanrei-comp-button-container-height-medium — 40px
--tanrei-comp-button-container-height-small — 32px
--tanrei-comp-button-container-padding-block-large — 6px
--tanrei-comp-button-container-padding-block-medium — 6px
--tanrei-comp-button-container-padding-block-small — 6px
--tanrei-comp-button-container-padding-inline-end-with-icon-large sys.space.space200 16px
--tanrei-comp-button-container-padding-inline-end-with-icon-medium sys.space.space150 12px
--tanrei-comp-button-container-padding-inline-end-with-icon-small sys.space.space150 12px
--tanrei-comp-button-container-padding-inline-end-large sys.space.space300 24px
--tanrei-comp-button-container-padding-inline-end-medium sys.space.space200 16px
--tanrei-comp-button-container-padding-inline-end-small sys.space.space200 16px
--tanrei-comp-button-container-padding-inline-narrow-large sys.space.space50 4px
--tanrei-comp-button-container-padding-inline-narrow-medium sys.space.space50 4px
--tanrei-comp-button-container-padding-inline-narrow-small sys.space.space50 4px
--tanrei-comp-button-container-padding-inline-start-with-icon-large sys.space.space200 16px
--tanrei-comp-button-container-padding-inline-start-with-icon-medium sys.space.space150 12px
--tanrei-comp-button-container-padding-inline-start-with-icon-small sys.space.space150 12px
--tanrei-comp-button-container-padding-inline-start-large sys.space.space300 24px
--tanrei-comp-button-container-padding-inline-start-medium sys.space.space200 16px
--tanrei-comp-button-container-padding-inline-start-small sys.space.space200 16px
--tanrei-comp-button-container-shape sys.shape.corner.full 9999px
--tanrei-comp-button-container-width-wide-large — 72px
--tanrei-comp-button-container-width-wide-medium — 52px
--tanrei-comp-button-container-width-wide-small — 40px
--tanrei-comp-button-disabled-container-shape — 9999px
--tanrei-comp-button-disabled-opacity — 1
--tanrei-comp-button-elevated-container-color sys.color.surface-container-lowest #ffffff
--tanrei-comp-button-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-button-elevated-destructive-container-color comp.button.elevated.container.color #ffffff
--tanrei-comp-button-elevated-destructive-icon-color comp.button.elevated.destructive.label.color #cd262f
--tanrei-comp-button-elevated-destructive-label-color sys.color.error #cd262f
--tanrei-comp-button-elevated-destructive-outline-color comp.button.elevated.outline.color #000000 · 0%
--tanrei-comp-button-elevated-destructive-state-layer-color comp.button.elevated.destructive.label.color #cd262f
--tanrei-comp-button-elevated-disabled-container-color — #1b1b1b · 12%
--tanrei-comp-button-elevated-disabled-container-elevation —
--tanrei-comp-button-elevated-disabled-destructive-container-color — #1b1b1b · 12%
--tanrei-comp-button-elevated-disabled-destructive-icon-color — #1b1b1b · 38%
--tanrei-comp-button-elevated-disabled-destructive-label-color — #1b1b1b · 38%
--tanrei-comp-button-elevated-disabled-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-disabled-destructive-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-elevated-disabled-icon-color — #1b1b1b · 38%
--tanrei-comp-button-elevated-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-button-elevated-disabled-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-disabled-selected-container-color — #1b1b1b · 12%
--tanrei-comp-button-elevated-disabled-selected-icon-color — #1b1b1b · 38%
--tanrei-comp-button-elevated-disabled-selected-label-color — #1b1b1b · 38%
--tanrei-comp-button-elevated-disabled-selected-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-disabled-selected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-elevated-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-elevated-disabled-unselected-container-color — #1b1b1b · 12%
--tanrei-comp-button-elevated-disabled-unselected-icon-color — #1b1b1b · 38%
--tanrei-comp-button-elevated-disabled-unselected-label-color — #1b1b1b · 38%
--tanrei-comp-button-elevated-disabled-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-disabled-unselected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-elevated-focused-container-color — #e8e8e8
--tanrei-comp-button-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-button-elevated-focused-destructive-container-color — #fae9ea
--tanrei-comp-button-elevated-focused-destructive-icon-color — #cd262f
--tanrei-comp-button-elevated-focused-destructive-label-color — #cd262f
--tanrei-comp-button-elevated-focused-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-focused-destructive-state-layer-color — #cd262f
--tanrei-comp-button-elevated-focused-icon-color — #1b1b1b
--tanrei-comp-button-elevated-focused-label-color — #1b1b1b
--tanrei-comp-button-elevated-focused-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-focused-selected-container-color — #323232
--tanrei-comp-button-elevated-focused-selected-icon-color — #ffffff
--tanrei-comp-button-elevated-focused-selected-label-color — #ffffff
--tanrei-comp-button-elevated-focused-selected-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-focused-selected-state-layer-color — #ffffff
--tanrei-comp-button-elevated-focused-state-layer-color — #1b1b1b
--tanrei-comp-button-elevated-focused-unselected-container-color — #ededed
--tanrei-comp-button-elevated-focused-unselected-icon-color — #4b4b4b
--tanrei-comp-button-elevated-focused-unselected-label-color — #4b4b4b
--tanrei-comp-button-elevated-focused-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-focused-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-elevated-hovered-container-color — #ededed
--tanrei-comp-button-elevated-hovered-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-button-elevated-hovered-destructive-container-color — #fbeeee
--tanrei-comp-button-elevated-hovered-destructive-icon-color — #cd262f
--tanrei-comp-button-elevated-hovered-destructive-label-color — #cd262f
--tanrei-comp-button-elevated-hovered-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-hovered-destructive-state-layer-color — #cd262f
--tanrei-comp-button-elevated-hovered-icon-color — #1b1b1b
--tanrei-comp-button-elevated-hovered-label-color — #1b1b1b
--tanrei-comp-button-elevated-hovered-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-hovered-selected-container-color — #2d2d2d
--tanrei-comp-button-elevated-hovered-selected-icon-color — #ffffff
--tanrei-comp-button-elevated-hovered-selected-label-color — #ffffff
--tanrei-comp-button-elevated-hovered-selected-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-hovered-selected-state-layer-color — #ffffff
--tanrei-comp-button-elevated-hovered-state-layer-color — #1b1b1b
--tanrei-comp-button-elevated-hovered-unselected-container-color — #f1f1f1
--tanrei-comp-button-elevated-hovered-unselected-icon-color — #4b4b4b
--tanrei-comp-button-elevated-hovered-unselected-label-color — #4b4b4b
--tanrei-comp-button-elevated-hovered-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-hovered-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-elevated-icon-color comp.button.elevated.label.color #1b1b1b
--tanrei-comp-button-elevated-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-button-elevated-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-pressed-container-color — #e8e8e8
--tanrei-comp-button-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-button-elevated-pressed-destructive-container-color — #fae9ea
--tanrei-comp-button-elevated-pressed-destructive-icon-color — #cd262f
--tanrei-comp-button-elevated-pressed-destructive-label-color — #cd262f
--tanrei-comp-button-elevated-pressed-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-pressed-destructive-state-layer-color — #cd262f
--tanrei-comp-button-elevated-pressed-icon-color — #1b1b1b
--tanrei-comp-button-elevated-pressed-label-color — #1b1b1b
--tanrei-comp-button-elevated-pressed-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-pressed-selected-container-color — #323232
--tanrei-comp-button-elevated-pressed-selected-icon-color — #ffffff
--tanrei-comp-button-elevated-pressed-selected-label-color — #ffffff
--tanrei-comp-button-elevated-pressed-selected-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-pressed-selected-state-layer-color — #ffffff
--tanrei-comp-button-elevated-pressed-state-layer-color — #1b1b1b
--tanrei-comp-button-elevated-pressed-unselected-container-color — #ededed
--tanrei-comp-button-elevated-pressed-unselected-icon-color — #4b4b4b
--tanrei-comp-button-elevated-pressed-unselected-label-color — #4b4b4b
--tanrei-comp-button-elevated-pressed-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-pressed-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-elevated-selected-container-color sys.color.primary #1b1b1b
--tanrei-comp-button-elevated-selected-icon-color comp.button.elevated.selected.label.color #ffffff
--tanrei-comp-button-elevated-selected-label-color sys.color.on-primary #ffffff
--tanrei-comp-button-elevated-selected-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-selected-state-layer-color comp.button.elevated.selected.label.color #ffffff
--tanrei-comp-button-elevated-state-layer-color comp.button.elevated.label.color #1b1b1b
--tanrei-comp-button-elevated-unselected-container-color sys.color.surface-container-lowest #ffffff
--tanrei-comp-button-elevated-unselected-icon-color comp.button.elevated.unselected.label.color #4b4b4b
--tanrei-comp-button-elevated-unselected-label-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-button-elevated-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-elevated-unselected-state-layer-color comp.button.elevated.unselected.label.color #4b4b4b
--tanrei-comp-button-filled-container-color sys.color.primary #1b1b1b
--tanrei-comp-button-filled-container-elevation sys.elevation.level0
--tanrei-comp-button-filled-destructive-container-color sys.color.error #cd262f
--tanrei-comp-button-filled-destructive-icon-color comp.button.filled.destructive.label.color #ffffff
--tanrei-comp-button-filled-destructive-label-color sys.color.on-error #ffffff
--tanrei-comp-button-filled-destructive-outline-color comp.button.filled.outline.color #000000 · 0%
--tanrei-comp-button-filled-destructive-state-layer-color comp.button.filled.destructive.label.color #ffffff
--tanrei-comp-button-filled-disabled-container-color — #1b1b1b · 12%
--tanrei-comp-button-filled-disabled-container-elevation —
--tanrei-comp-button-filled-disabled-destructive-container-color — #1b1b1b · 12%
--tanrei-comp-button-filled-disabled-destructive-icon-color — #1b1b1b · 38%
--tanrei-comp-button-filled-disabled-destructive-label-color — #1b1b1b · 38%
--tanrei-comp-button-filled-disabled-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-filled-disabled-destructive-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-filled-disabled-icon-color — #1b1b1b · 38%
--tanrei-comp-button-filled-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-button-filled-disabled-outline-color — #000000 · 0%
--tanrei-comp-button-filled-disabled-selected-container-color — #1b1b1b · 12%
--tanrei-comp-button-filled-disabled-selected-icon-color — #1b1b1b · 38%
--tanrei-comp-button-filled-disabled-selected-label-color — #1b1b1b · 38%
--tanrei-comp-button-filled-disabled-selected-outline-color — #000000 · 0%
--tanrei-comp-button-filled-disabled-selected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-filled-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-filled-disabled-unselected-container-color — #1b1b1b · 12%
--tanrei-comp-button-filled-disabled-unselected-icon-color — #1b1b1b · 38%
--tanrei-comp-button-filled-disabled-unselected-label-color — #1b1b1b · 38%
--tanrei-comp-button-filled-disabled-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-filled-disabled-unselected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-filled-focused-container-color — #323232
--tanrei-comp-button-filled-focused-container-elevation —
--tanrei-comp-button-filled-focused-destructive-container-color — #d23c44
--tanrei-comp-button-filled-focused-destructive-icon-color — #ffffff
--tanrei-comp-button-filled-focused-destructive-label-color — #ffffff
--tanrei-comp-button-filled-focused-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-filled-focused-destructive-state-layer-color — #ffffff
--tanrei-comp-button-filled-focused-icon-color — #ffffff
--tanrei-comp-button-filled-focused-label-color — #ffffff
--tanrei-comp-button-filled-focused-outline-color — #000000 · 0%
--tanrei-comp-button-filled-focused-selected-container-color — #323232
--tanrei-comp-button-filled-focused-selected-icon-color — #ffffff
--tanrei-comp-button-filled-focused-selected-label-color — #ffffff
--tanrei-comp-button-filled-focused-selected-outline-color — #000000 · 0%
--tanrei-comp-button-filled-focused-selected-state-layer-color — #ffffff
--tanrei-comp-button-filled-focused-state-layer-color — #ffffff
--tanrei-comp-button-filled-focused-unselected-container-color — #dedede
--tanrei-comp-button-filled-focused-unselected-icon-color — #4b4b4b
--tanrei-comp-button-filled-focused-unselected-label-color — #4b4b4b
--tanrei-comp-button-filled-focused-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-filled-focused-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-filled-hovered-container-color — #2d2d2d
--tanrei-comp-button-filled-hovered-container-elevation —
--tanrei-comp-button-filled-hovered-destructive-container-color — #d13740
--tanrei-comp-button-filled-hovered-destructive-icon-color — #ffffff
--tanrei-comp-button-filled-hovered-destructive-label-color — #ffffff
--tanrei-comp-button-filled-hovered-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-filled-hovered-destructive-state-layer-color — #ffffff
--tanrei-comp-button-filled-hovered-icon-color — #ffffff
--tanrei-comp-button-filled-hovered-label-color — #ffffff
--tanrei-comp-button-filled-hovered-outline-color — #000000 · 0%
--tanrei-comp-button-filled-hovered-selected-container-color — #2d2d2d
--tanrei-comp-button-filled-hovered-selected-icon-color — #ffffff
--tanrei-comp-button-filled-hovered-selected-label-color — #ffffff
--tanrei-comp-button-filled-hovered-selected-outline-color — #000000 · 0%
--tanrei-comp-button-filled-hovered-selected-state-layer-color — #ffffff
--tanrei-comp-button-filled-hovered-state-layer-color — #ffffff
--tanrei-comp-button-filled-hovered-unselected-container-color — #e1e1e1
--tanrei-comp-button-filled-hovered-unselected-icon-color — #4b4b4b
--tanrei-comp-button-filled-hovered-unselected-label-color — #4b4b4b
--tanrei-comp-button-filled-hovered-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-filled-hovered-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-filled-icon-color comp.button.filled.label.color #ffffff
--tanrei-comp-button-filled-label-color sys.color.on-primary #ffffff
--tanrei-comp-button-filled-outline-color — #000000 · 0%
--tanrei-comp-button-filled-pressed-container-color — #323232
--tanrei-comp-button-filled-pressed-container-elevation —
--tanrei-comp-button-filled-pressed-destructive-container-color — #d23c44
--tanrei-comp-button-filled-pressed-destructive-icon-color — #ffffff
--tanrei-comp-button-filled-pressed-destructive-label-color — #ffffff
--tanrei-comp-button-filled-pressed-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-filled-pressed-destructive-state-layer-color — #ffffff
--tanrei-comp-button-filled-pressed-icon-color — #ffffff
--tanrei-comp-button-filled-pressed-label-color — #ffffff
--tanrei-comp-button-filled-pressed-outline-color — #000000 · 0%
--tanrei-comp-button-filled-pressed-selected-container-color — #323232
--tanrei-comp-button-filled-pressed-selected-icon-color — #ffffff
--tanrei-comp-button-filled-pressed-selected-label-color — #ffffff
--tanrei-comp-button-filled-pressed-selected-outline-color — #000000 · 0%
--tanrei-comp-button-filled-pressed-selected-state-layer-color — #ffffff
--tanrei-comp-button-filled-pressed-state-layer-color — #ffffff
--tanrei-comp-button-filled-pressed-unselected-container-color — #dedede
--tanrei-comp-button-filled-pressed-unselected-icon-color — #4b4b4b
--tanrei-comp-button-filled-pressed-unselected-label-color — #4b4b4b
--tanrei-comp-button-filled-pressed-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-filled-pressed-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-filled-selected-container-color sys.color.primary #1b1b1b
--tanrei-comp-button-filled-selected-icon-color comp.button.filled.selected.label.color #ffffff
--tanrei-comp-button-filled-selected-label-color sys.color.on-primary #ffffff
--tanrei-comp-button-filled-selected-outline-color — #000000 · 0%
--tanrei-comp-button-filled-selected-state-layer-color comp.button.filled.selected.label.color #ffffff
--tanrei-comp-button-filled-state-layer-color comp.button.filled.label.color #ffffff
--tanrei-comp-button-filled-unselected-container-color sys.color.surface-container-highest #eeeeee
--tanrei-comp-button-filled-unselected-icon-color comp.button.filled.unselected.label.color #4b4b4b
--tanrei-comp-button-filled-unselected-label-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-button-filled-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-filled-unselected-state-layer-color comp.button.filled.unselected.label.color #4b4b4b
--tanrei-comp-button-focused-container-shape — 9999px
--tanrei-comp-button-focused-opacity — 1
--tanrei-comp-button-hovered-container-shape — 9999px
--tanrei-comp-button-hovered-opacity — 1
--tanrei-comp-button-icon-size-without-label-large comp.button.icon.size.large 24px
--tanrei-comp-button-icon-size-without-label-medium comp.button.icon.size.medium 24px
--tanrei-comp-button-icon-size-without-label-small comp.button.icon.size.small 20px
--tanrei-comp-button-icon-size-large sys.icon.medium 24px
--tanrei-comp-button-icon-size-medium sys.icon.medium 24px
--tanrei-comp-button-icon-size-small sys.icon.small 20px
--tanrei-comp-button-motion-busy-cycle-duration comp.loading-indicator.motion.cycle-duration 1000ms
--tanrei-comp-button-motion-feedback-duration sys.motion.role.feedback 450ms
--tanrei-comp-button-motion-state-duration-large sys.motion.role.state.large 250ms
--tanrei-comp-button-motion-state-duration-medium sys.motion.role.state.default 250ms
--tanrei-comp-button-motion-state-duration-small sys.motion.role.state.compact 200ms
--tanrei-comp-button-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-button-outline-width-large sys.border.hairline 1px
--tanrei-comp-button-outline-width-medium sys.border.hairline 1px
--tanrei-comp-button-outline-width-small sys.border.hairline 1px
--tanrei-comp-button-outlined-container-color — #000000 · 0%
--tanrei-comp-button-outlined-container-elevation sys.elevation.level0
--tanrei-comp-button-outlined-destructive-container-color comp.button.outlined.container.color #000000 · 0%
--tanrei-comp-button-outlined-destructive-icon-color comp.button.outlined.destructive.label.color #cd262f
--tanrei-comp-button-outlined-destructive-label-color sys.color.error #cd262f
--tanrei-comp-button-outlined-destructive-outline-color sys.color.error #cd262f
--tanrei-comp-button-outlined-destructive-state-layer-color comp.button.outlined.destructive.label.color #cd262f
--tanrei-comp-button-outlined-disabled-container-color — #000000 · 0%
--tanrei-comp-button-outlined-disabled-container-elevation —
--tanrei-comp-button-outlined-disabled-destructive-container-color — #000000 · 0%
--tanrei-comp-button-outlined-disabled-destructive-icon-color — #1b1b1b · 38%
--tanrei-comp-button-outlined-disabled-destructive-label-color — #1b1b1b · 38%
--tanrei-comp-button-outlined-disabled-destructive-outline-color — #000000 · 8%
--tanrei-comp-button-outlined-disabled-destructive-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-outlined-disabled-icon-color — #1b1b1b · 38%
--tanrei-comp-button-outlined-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-button-outlined-disabled-outline-color — #000000 · 8%
--tanrei-comp-button-outlined-disabled-selected-container-color — #1b1b1b · 12%
--tanrei-comp-button-outlined-disabled-selected-icon-color — #1b1b1b · 38%
--tanrei-comp-button-outlined-disabled-selected-label-color — #1b1b1b · 38%
--tanrei-comp-button-outlined-disabled-selected-outline-color — #000000 · 0%
--tanrei-comp-button-outlined-disabled-selected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-outlined-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-outlined-disabled-unselected-container-color — #000000 · 0%
--tanrei-comp-button-outlined-disabled-unselected-icon-color — #1b1b1b · 38%
--tanrei-comp-button-outlined-disabled-unselected-label-color — #1b1b1b · 38%
--tanrei-comp-button-outlined-disabled-unselected-outline-color — #000000 · 8%
--tanrei-comp-button-outlined-disabled-unselected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-outlined-focused-container-color — #1b1b1b · 10%
--tanrei-comp-button-outlined-focused-container-elevation —
--tanrei-comp-button-outlined-focused-destructive-container-color — #cd262f · 10%
--tanrei-comp-button-outlined-focused-destructive-icon-color — #cd262f
--tanrei-comp-button-outlined-focused-destructive-label-color — #cd262f
--tanrei-comp-button-outlined-focused-destructive-outline-color — #cd262f
--tanrei-comp-button-outlined-focused-destructive-state-layer-color — #cd262f
--tanrei-comp-button-outlined-focused-icon-color — #4b4b4b
--tanrei-comp-button-outlined-focused-label-color — #1b1b1b
--tanrei-comp-button-outlined-focused-outline-color — #000000 · 8%
--tanrei-comp-button-outlined-focused-selected-container-color — #343434
--tanrei-comp-button-outlined-focused-selected-icon-color — #f1f1f1
--tanrei-comp-button-outlined-focused-selected-label-color — #f1f1f1
--tanrei-comp-button-outlined-focused-selected-outline-color — #000000 · 0%
--tanrei-comp-button-outlined-focused-selected-state-layer-color — #f1f1f1
--tanrei-comp-button-outlined-focused-state-layer-color — #1b1b1b
--tanrei-comp-button-outlined-focused-unselected-container-color — #4b4b4b · 10%
--tanrei-comp-button-outlined-focused-unselected-icon-color — #4b4b4b
--tanrei-comp-button-outlined-focused-unselected-label-color — #4b4b4b
--tanrei-comp-button-outlined-focused-unselected-outline-color — #000000 · 8%
--tanrei-comp-button-outlined-focused-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-outlined-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-button-outlined-hovered-container-elevation —
--tanrei-comp-button-outlined-hovered-destructive-container-color — #cd262f · 8%
--tanrei-comp-button-outlined-hovered-destructive-icon-color — #cd262f
--tanrei-comp-button-outlined-hovered-destructive-label-color — #cd262f
--tanrei-comp-button-outlined-hovered-destructive-outline-color — #cd262f
--tanrei-comp-button-outlined-hovered-destructive-state-layer-color — #cd262f
--tanrei-comp-button-outlined-hovered-icon-color — #4b4b4b
--tanrei-comp-button-outlined-hovered-label-color — #1b1b1b
--tanrei-comp-button-outlined-hovered-outline-color — #000000 · 8%
--tanrei-comp-button-outlined-hovered-selected-container-color — #303030
--tanrei-comp-button-outlined-hovered-selected-icon-color — #f1f1f1
--tanrei-comp-button-outlined-hovered-selected-label-color — #f1f1f1
--tanrei-comp-button-outlined-hovered-selected-outline-color — #000000 · 0%
--tanrei-comp-button-outlined-hovered-selected-state-layer-color — #f1f1f1
--tanrei-comp-button-outlined-hovered-state-layer-color — #1b1b1b
--tanrei-comp-button-outlined-hovered-unselected-container-color — #4b4b4b · 8%
--tanrei-comp-button-outlined-hovered-unselected-icon-color — #4b4b4b
--tanrei-comp-button-outlined-hovered-unselected-label-color — #4b4b4b
--tanrei-comp-button-outlined-hovered-unselected-outline-color — #000000 · 8%
--tanrei-comp-button-outlined-hovered-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-outlined-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-button-outlined-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-button-outlined-outline-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-button-outlined-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-button-outlined-pressed-container-elevation —
--tanrei-comp-button-outlined-pressed-destructive-container-color — #cd262f · 10%
--tanrei-comp-button-outlined-pressed-destructive-icon-color — #cd262f
--tanrei-comp-button-outlined-pressed-destructive-label-color — #cd262f
--tanrei-comp-button-outlined-pressed-destructive-outline-color — #cd262f
--tanrei-comp-button-outlined-pressed-destructive-state-layer-color — #cd262f
--tanrei-comp-button-outlined-pressed-icon-color — #4b4b4b
--tanrei-comp-button-outlined-pressed-label-color — #1b1b1b
--tanrei-comp-button-outlined-pressed-outline-color — #000000 · 8%
--tanrei-comp-button-outlined-pressed-selected-container-color — #343434
--tanrei-comp-button-outlined-pressed-selected-icon-color — #f1f1f1
--tanrei-comp-button-outlined-pressed-selected-label-color — #f1f1f1
--tanrei-comp-button-outlined-pressed-selected-outline-color — #000000 · 0%
--tanrei-comp-button-outlined-pressed-selected-state-layer-color — #f1f1f1
--tanrei-comp-button-outlined-pressed-state-layer-color — #1b1b1b
--tanrei-comp-button-outlined-pressed-unselected-container-color — #4b4b4b · 10%
--tanrei-comp-button-outlined-pressed-unselected-icon-color — #4b4b4b
--tanrei-comp-button-outlined-pressed-unselected-label-color — #4b4b4b
--tanrei-comp-button-outlined-pressed-unselected-outline-color — #000000 · 8%
--tanrei-comp-button-outlined-pressed-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-outlined-selected-container-color sys.color.inverse-surface #1f1f1f
--tanrei-comp-button-outlined-selected-icon-color comp.button.outlined.selected.label.color #f1f1f1
--tanrei-comp-button-outlined-selected-label-color sys.color.inverse-on-surface #f1f1f1
--tanrei-comp-button-outlined-selected-outline-color — #000000 · 0%
--tanrei-comp-button-outlined-selected-state-layer-color comp.button.outlined.selected.label.color #f1f1f1
--tanrei-comp-button-outlined-state-layer-color comp.button.outlined.label.color #1b1b1b
--tanrei-comp-button-outlined-unselected-container-color — #000000 · 0%
--tanrei-comp-button-outlined-unselected-icon-color comp.button.outlined.unselected.label.color #4b4b4b
--tanrei-comp-button-outlined-unselected-label-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-button-outlined-unselected-outline-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-button-outlined-unselected-state-layer-color comp.button.outlined.unselected.label.color #4b4b4b
--tanrei-comp-button-pressed-container-shape — 9999px
--tanrei-comp-button-pressed-opacity — 1
--tanrei-comp-button-text-container-color — #000000 · 0%
--tanrei-comp-button-text-container-elevation sys.elevation.level0
--tanrei-comp-button-text-destructive-container-color comp.button.text.container.color #000000 · 0%
--tanrei-comp-button-text-destructive-icon-color comp.button.text.destructive.label.color #cd262f
--tanrei-comp-button-text-destructive-label-color sys.color.error #cd262f
--tanrei-comp-button-text-destructive-outline-color comp.button.text.outline.color #000000 · 0%
--tanrei-comp-button-text-destructive-state-layer-color comp.button.text.destructive.label.color #cd262f
--tanrei-comp-button-text-disabled-container-color — #000000 · 0%
--tanrei-comp-button-text-disabled-container-elevation —
--tanrei-comp-button-text-disabled-destructive-container-color — #000000 · 0%
--tanrei-comp-button-text-disabled-destructive-icon-color — #1b1b1b · 38%
--tanrei-comp-button-text-disabled-destructive-label-color — #1b1b1b · 38%
--tanrei-comp-button-text-disabled-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-text-disabled-destructive-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-text-disabled-icon-color — #1b1b1b · 38%
--tanrei-comp-button-text-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-button-text-disabled-outline-color — #000000 · 0%
--tanrei-comp-button-text-disabled-selected-container-color — #000000 · 0%
--tanrei-comp-button-text-disabled-selected-icon-color — #1b1b1b · 38%
--tanrei-comp-button-text-disabled-selected-label-color — #1b1b1b · 38%
--tanrei-comp-button-text-disabled-selected-outline-color — #000000 · 0%
--tanrei-comp-button-text-disabled-selected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-text-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-text-disabled-unselected-container-color — #000000 · 0%
--tanrei-comp-button-text-disabled-unselected-icon-color — #1b1b1b · 38%
--tanrei-comp-button-text-disabled-unselected-label-color — #1b1b1b · 38%
--tanrei-comp-button-text-disabled-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-text-disabled-unselected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-text-focused-container-color — #1b1b1b · 10%
--tanrei-comp-button-text-focused-container-elevation —
--tanrei-comp-button-text-focused-destructive-container-color — #cd262f · 10%
--tanrei-comp-button-text-focused-destructive-icon-color — #cd262f
--tanrei-comp-button-text-focused-destructive-label-color — #cd262f
--tanrei-comp-button-text-focused-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-text-focused-destructive-state-layer-color — #cd262f
--tanrei-comp-button-text-focused-icon-color — #4b4b4b
--tanrei-comp-button-text-focused-label-color — #1b1b1b
--tanrei-comp-button-text-focused-outline-color — #000000 · 0%
--tanrei-comp-button-text-focused-selected-container-color — #1b1b1b · 10%
--tanrei-comp-button-text-focused-selected-icon-color — #1b1b1b
--tanrei-comp-button-text-focused-selected-label-color — #1b1b1b
--tanrei-comp-button-text-focused-selected-outline-color — #000000 · 0%
--tanrei-comp-button-text-focused-selected-state-layer-color — #1b1b1b
--tanrei-comp-button-text-focused-state-layer-color — #1b1b1b
--tanrei-comp-button-text-focused-unselected-container-color — #4b4b4b · 10%
--tanrei-comp-button-text-focused-unselected-icon-color — #4b4b4b
--tanrei-comp-button-text-focused-unselected-label-color — #4b4b4b
--tanrei-comp-button-text-focused-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-text-focused-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-text-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-button-text-hovered-container-elevation —
--tanrei-comp-button-text-hovered-destructive-container-color — #cd262f · 8%
--tanrei-comp-button-text-hovered-destructive-icon-color — #cd262f
--tanrei-comp-button-text-hovered-destructive-label-color — #cd262f
--tanrei-comp-button-text-hovered-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-text-hovered-destructive-state-layer-color — #cd262f
--tanrei-comp-button-text-hovered-icon-color — #4b4b4b
--tanrei-comp-button-text-hovered-label-color — #1b1b1b
--tanrei-comp-button-text-hovered-outline-color — #000000 · 0%
--tanrei-comp-button-text-hovered-selected-container-color — #1b1b1b · 8%
--tanrei-comp-button-text-hovered-selected-icon-color — #1b1b1b
--tanrei-comp-button-text-hovered-selected-label-color — #1b1b1b
--tanrei-comp-button-text-hovered-selected-outline-color — #000000 · 0%
--tanrei-comp-button-text-hovered-selected-state-layer-color — #1b1b1b
--tanrei-comp-button-text-hovered-state-layer-color — #1b1b1b
--tanrei-comp-button-text-hovered-unselected-container-color — #4b4b4b · 8%
--tanrei-comp-button-text-hovered-unselected-icon-color — #4b4b4b
--tanrei-comp-button-text-hovered-unselected-label-color — #4b4b4b
--tanrei-comp-button-text-hovered-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-text-hovered-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-text-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-button-text-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-button-text-outline-color — #000000 · 0%
--tanrei-comp-button-text-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-button-text-pressed-container-elevation —
--tanrei-comp-button-text-pressed-destructive-container-color — #cd262f · 10%
--tanrei-comp-button-text-pressed-destructive-icon-color — #cd262f
--tanrei-comp-button-text-pressed-destructive-label-color — #cd262f
--tanrei-comp-button-text-pressed-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-text-pressed-destructive-state-layer-color — #cd262f
--tanrei-comp-button-text-pressed-icon-color — #4b4b4b
--tanrei-comp-button-text-pressed-label-color — #1b1b1b
--tanrei-comp-button-text-pressed-outline-color — #000000 · 0%
--tanrei-comp-button-text-pressed-selected-container-color — #1b1b1b · 10%
--tanrei-comp-button-text-pressed-selected-icon-color — #1b1b1b
--tanrei-comp-button-text-pressed-selected-label-color — #1b1b1b
--tanrei-comp-button-text-pressed-selected-outline-color — #000000 · 0%
--tanrei-comp-button-text-pressed-selected-state-layer-color — #1b1b1b
--tanrei-comp-button-text-pressed-state-layer-color — #1b1b1b
--tanrei-comp-button-text-pressed-unselected-container-color — #4b4b4b · 10%
--tanrei-comp-button-text-pressed-unselected-icon-color — #4b4b4b
--tanrei-comp-button-text-pressed-unselected-label-color — #4b4b4b
--tanrei-comp-button-text-pressed-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-text-pressed-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-text-selected-container-color — #000000 · 0%
--tanrei-comp-button-text-selected-icon-color comp.button.text.selected.label.color #1b1b1b
--tanrei-comp-button-text-selected-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-button-text-selected-outline-color — #000000 · 0%
--tanrei-comp-button-text-selected-state-layer-color comp.button.text.selected.label.color #1b1b1b
--tanrei-comp-button-text-state-layer-color comp.button.text.label.color #1b1b1b
--tanrei-comp-button-text-unselected-container-color — #000000 · 0%
--tanrei-comp-button-text-unselected-icon-color comp.button.text.unselected.label.color #4b4b4b
--tanrei-comp-button-text-unselected-label-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-button-text-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-text-unselected-state-layer-color comp.button.text.unselected.label.color #4b4b4b
--tanrei-comp-button-tonal-container-color sys.color.secondary-container #eeeeee
--tanrei-comp-button-tonal-container-elevation sys.elevation.level0
--tanrei-comp-button-tonal-destructive-container-color sys.color.error-container #ffedeb
--tanrei-comp-button-tonal-destructive-icon-color comp.button.tonal.destructive.label.color #3a211f
--tanrei-comp-button-tonal-destructive-label-color sys.color.on-error-container #3a211f
--tanrei-comp-button-tonal-destructive-outline-color comp.button.tonal.outline.color #000000 · 0%
--tanrei-comp-button-tonal-destructive-state-layer-color comp.button.tonal.destructive.label.color #3a211f
--tanrei-comp-button-tonal-disabled-container-color — #1b1b1b · 12%
--tanrei-comp-button-tonal-disabled-container-elevation —
--tanrei-comp-button-tonal-disabled-destructive-container-color — #1b1b1b · 12%
--tanrei-comp-button-tonal-disabled-destructive-icon-color — #1b1b1b · 38%
--tanrei-comp-button-tonal-disabled-destructive-label-color — #1b1b1b · 38%
--tanrei-comp-button-tonal-disabled-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-disabled-destructive-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-tonal-disabled-icon-color — #1b1b1b · 38%
--tanrei-comp-button-tonal-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-button-tonal-disabled-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-disabled-selected-container-color — #1b1b1b · 12%
--tanrei-comp-button-tonal-disabled-selected-icon-color — #1b1b1b · 38%
--tanrei-comp-button-tonal-disabled-selected-label-color — #1b1b1b · 38%
--tanrei-comp-button-tonal-disabled-selected-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-disabled-selected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-tonal-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-tonal-disabled-unselected-container-color — #1b1b1b · 12%
--tanrei-comp-button-tonal-disabled-unselected-icon-color — #1b1b1b · 38%
--tanrei-comp-button-tonal-disabled-unselected-label-color — #1b1b1b · 38%
--tanrei-comp-button-tonal-disabled-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-disabled-unselected-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-tonal-focused-container-color — #dadada
--tanrei-comp-button-tonal-focused-container-elevation —
--tanrei-comp-button-tonal-focused-destructive-container-color — #ebd9d7
--tanrei-comp-button-tonal-focused-destructive-icon-color — #3a211f
--tanrei-comp-button-tonal-focused-destructive-label-color — #3a211f
--tanrei-comp-button-tonal-focused-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-focused-destructive-state-layer-color — #3a211f
--tanrei-comp-button-tonal-focused-icon-color — #262626
--tanrei-comp-button-tonal-focused-label-color — #262626
--tanrei-comp-button-tonal-focused-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-focused-selected-container-color — #636363
--tanrei-comp-button-tonal-focused-selected-icon-color — #ffffff
--tanrei-comp-button-tonal-focused-selected-label-color — #ffffff
--tanrei-comp-button-tonal-focused-selected-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-focused-selected-state-layer-color — #ffffff
--tanrei-comp-button-tonal-focused-state-layer-color — #262626
--tanrei-comp-button-tonal-focused-unselected-container-color — #dedede
--tanrei-comp-button-tonal-focused-unselected-icon-color — #4b4b4b
--tanrei-comp-button-tonal-focused-unselected-label-color — #4b4b4b
--tanrei-comp-button-tonal-focused-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-focused-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-tonal-hovered-container-color — #dedede
--tanrei-comp-button-tonal-hovered-container-elevation —
--tanrei-comp-button-tonal-hovered-destructive-container-color — #efdddb
--tanrei-comp-button-tonal-hovered-destructive-icon-color — #3a211f
--tanrei-comp-button-tonal-hovered-destructive-label-color — #3a211f
--tanrei-comp-button-tonal-hovered-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-hovered-destructive-state-layer-color — #3a211f
--tanrei-comp-button-tonal-hovered-icon-color — #262626
--tanrei-comp-button-tonal-hovered-label-color — #262626
--tanrei-comp-button-tonal-hovered-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-hovered-selected-container-color — #606060
--tanrei-comp-button-tonal-hovered-selected-icon-color — #ffffff
--tanrei-comp-button-tonal-hovered-selected-label-color — #ffffff
--tanrei-comp-button-tonal-hovered-selected-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-hovered-selected-state-layer-color — #ffffff
--tanrei-comp-button-tonal-hovered-state-layer-color — #262626
--tanrei-comp-button-tonal-hovered-unselected-container-color — #e1e1e1
--tanrei-comp-button-tonal-hovered-unselected-icon-color — #4b4b4b
--tanrei-comp-button-tonal-hovered-unselected-label-color — #4b4b4b
--tanrei-comp-button-tonal-hovered-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-hovered-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-tonal-icon-color comp.button.tonal.label.color #262626
--tanrei-comp-button-tonal-label-color sys.color.on-secondary-container #262626
--tanrei-comp-button-tonal-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-pressed-container-color — #dadada
--tanrei-comp-button-tonal-pressed-container-elevation —
--tanrei-comp-button-tonal-pressed-destructive-container-color — #ebd9d7
--tanrei-comp-button-tonal-pressed-destructive-icon-color — #3a211f
--tanrei-comp-button-tonal-pressed-destructive-label-color — #3a211f
--tanrei-comp-button-tonal-pressed-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-pressed-destructive-state-layer-color — #3a211f
--tanrei-comp-button-tonal-pressed-icon-color — #262626
--tanrei-comp-button-tonal-pressed-label-color — #262626
--tanrei-comp-button-tonal-pressed-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-pressed-selected-container-color — #636363
--tanrei-comp-button-tonal-pressed-selected-icon-color — #ffffff
--tanrei-comp-button-tonal-pressed-selected-label-color — #ffffff
--tanrei-comp-button-tonal-pressed-selected-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-pressed-selected-state-layer-color — #ffffff
--tanrei-comp-button-tonal-pressed-state-layer-color — #262626
--tanrei-comp-button-tonal-pressed-unselected-container-color — #dedede
--tanrei-comp-button-tonal-pressed-unselected-icon-color — #4b4b4b
--tanrei-comp-button-tonal-pressed-unselected-label-color — #4b4b4b
--tanrei-comp-button-tonal-pressed-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-pressed-unselected-state-layer-color — #4b4b4b
--tanrei-comp-button-tonal-selected-container-color sys.color.secondary #525252
--tanrei-comp-button-tonal-selected-icon-color comp.button.tonal.selected.label.color #ffffff
--tanrei-comp-button-tonal-selected-label-color sys.color.on-secondary #ffffff
--tanrei-comp-button-tonal-selected-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-selected-state-layer-color comp.button.tonal.selected.label.color #ffffff
--tanrei-comp-button-tonal-state-layer-color comp.button.tonal.label.color #262626
--tanrei-comp-button-tonal-unselected-container-color sys.color.surface-container-highest #eeeeee
--tanrei-comp-button-tonal-unselected-icon-color comp.button.tonal.unselected.label.color #4b4b4b
--tanrei-comp-button-tonal-unselected-label-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-button-tonal-unselected-outline-color — #000000 · 0%
--tanrei-comp-button-tonal-unselected-state-layer-color comp.button.tonal.unselected.label.color #4b4b4b
--tanrei-comp-button-tone-container-opacity sys.state.selected-state-layer-opacity 0.16
--tanrei-comp-button-label-typescale-large sys.typescale.label 500 14px/1.4286
--tanrei-comp-button-label-typescale-medium sys.typescale.label 500 14px/1.4286
--tanrei-comp-button-label-typescale-small sys.typescale.label 500 14px/1.4286

测试样例

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

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

指南

何时使用

按钮用于在用户当前所在位置发生的操作:提交表单、保存、启动一个流程、确认对话框。在 Web 上,会提交所在表单的按钮用 type 说明这一点(参见在表单中)。

如果激活它只是把用户带到别处,那它就是链接。当这个目标值得用按钮来强调时,例如落地页上的“开始使用”或列表末尾的“查看全部”,就保留按钮并给它指定 href:这样它就是链接,拥有链接的角色、按键和浏览器行为(参见有目标地址的按钮是链接)。句子里的目标仍然用普通的文本链接。属于一段文字的操作,是放在这段文字后面的文本按钮,而不是文字里的一个短语:画得像链接却不是链接的东西,会让人不知道按下去会发生什么。

如何选择变体

变体表达强调程度,而强调是有限的预算。每个视图只花一次。

变体 强调程度 用于
Filled 最高 视图或对话框中最重要的那一个操作。
Tonal 高 重要但不应与 filled 按钮争抢注意力的操作,或次要区域中的主操作。
Elevated 中 必须从杂乱或带图案的背景中凸显出来的操作。
Outlined 中 放在 filled 按钮旁边、分量相当的操作,例如“取消”。
Text 低 最不紧迫的操作,以及卡片、对话框和工具栏中的行内操作。

destructive 不是变体:它可以与任何变体搭配,并保持该变体的强调程度(参见破坏性操作)。

没有 variant 的按钮是 filled,只有一个例外:作为对话框或侧边面板的操作放置时,它采用宿主的变体,在默认系统中是 tonal。因此只需要给外观应当不同的那个操作设置变体。每个宿主的关闭按钮——弹出框、侧边面板、底部面板、提示块、消息条和图片预览的——都是一个 small 的纯图标按钮,以同样的方式采用宿主为关闭按钮规定的变体,在默认系统中是 tonal。在带颜色的表面上,例如表示状态的提示块或反色的消息条上,它还会以该表面的文字颜色绘制。

推荐

  • 标签写成一个动词,或动词加名词,说明将会发生什么:部署、保存草稿、添加成员。
  • 每个视图只保留一个 filled 按钮。如果两个操作看起来同样重要,这个视图很可能在同时做两件事。
  • 把主操作放在该平台的用户预期的位置,并在整个产品中保持这一顺序。
  • 在密集的工具栏和表格中使用 small(32),默认使用 medium(40),在以触摸为主的界面或首屏主视觉区域使用 large(56)。每种尺寸的点击区域都有保证。
  • 给删除或丢弃某些东西的操作设置 destructive,并像其他操作一样按强调程度选择变体。
  • 通往删除某些东西之处的链接,做成普通链接,用标签说明它通向哪里。在那个地方执行删除的按钮上设置 destructive。
  • 用图标插槽来强化标签。只有在图标的含义广为人知时(搜索、关闭、更多)才省略标签,并用 label 为按钮命名。

避免

  • 不要在按钮的点击处理函数里进行导航。有目标地址的控件是链接:设置 href,它才能在新标签页中打开、被复制,并作为链接被朗读。
  • 不要给作用于当前页面的操作(例如“保存”或“删除”)指定 href,也不要不在 targetLabel 中说明就打开新标签页。
  • 不要在列表的每一行都放一个 filled 按钮。优先使用 text 按钮,或者在顶部放一个作用于所选项的操作。
  • 不要把禁用按钮当作传达校验问题的唯一手段。让它保持可用,并在被按下时说明还缺少什么;或者用 focusable-when-disabled 让它仍可到达,并说明原因。
  • 不要在按钮的操作进行期间禁用它。请设置 busy,让焦点留在它上面。
  • 不要为了强调而使用 destructive,也不要把它用在不会破坏任何东西的操作上。如果一个视图中的操作全是红色,就没有哪个能突出。
  • 不要把链接做成红色,也不要给按钮同时设置 href 和 destructive。链接只负责把用户带到别处,所以按钮会忽略 destructive,按其变体的链接来绘制。
  • 不要截断标签。让按钮变宽,或者把标签写短。
  • 不要让纯图标按钮缺少 label:图标不是名称。
  • 不要手动把 narrow 的纯图标按钮一个紧挨一个地排列。它们的目标会延伸到边缘之外,彼此重叠。

文案

标签末尾不加标点。以一到两个词为宜。按你的产品所用的每种语言书写标签的习惯来写:英语采用句首大写的写法(sentence case);日语使用 保存、デプロイ 这样的名词或动名词形式,而不是敬体的句尾。

无障碍

角色
button (link while href is set)
标注
A button that draws a label is named by that text, unless label is given: then label is the whole name, in place of the drawn text, and it must contain the drawn text (BTN-16). An icon-only button is named by label, which is required; its glyph is decorative to assistive technology. A tooltip may show the same text to sighted pointer users, but it never replaces label. A link button is named in the same way; its name should say where it goes. While target is new, targetLabel is its description ("opens in a new tab"), and the mark is decorative. A description is read after the name and is never the name: what tells one button from its neighbours belongs in the name, and what happens next belongs in the description.
最小目标
即使外观尺寸更小,点击区域也满足最小目标尺寸。 minimum 32px · pointer 44px · spacing 8px · touch 48px

键盘操作

按键 动作
Tab Moves focus to the button — also to a busy one, and to a disabled one while focusableWhenDisabled is true.
Enter / Space Activates the button; a toggle switches selected. A link button is activated by Enter alone: Space does what it does on any link (on the web it scrolls the page).
Enter in a field of the same form On the web, presses the form's default button — its first submit button — where that is a Button of type submit, and so submits the form; nothing while that button is disabled (BTN-30). Enter in a Tanrei field, checkbox, radio button or switch does the same where the default button is the platform's own.

备注

  • A toggle is a button with an on/off state (aria-pressed on the web, the selected trait on Apple platforms, a toggleable state on Android), not a checkbox or a switch; its name does not change with its state.
  • Text must remain readable at 200% text size: the container grows, the label never truncates by default.
  • Container heights are minimums: at larger text sizes the container grows in both axes. An icon-only button's glyph is not text, at any of its three widths: where sizes are points or dp (SwiftUI, Compose, Flutter, React Native), text size leaves its circle and its glyph as they are; on the web every Tanrei size follows the root font size (rem), so at 200% text size the circle and its glyph grow together and stay round and whole (BTN-12). Either way its label is still announced.
  • The icon's side follows the reading direction: leading is the start (left in left-to-right, right in right-to-left). A glyph whose name the system's icon set marks as mirrored (arrow_forward, chevron_right, reply) is mirrored in a right-to-left layout by the icon helper, not by the button, and the product can force or forbid that for one use (BTN-19).
  • How a state looks is a value decision of the active design system (a state-layer mix in Tanrei, possibly explicit colors, a dim, or no hover at all elsewhere). Components read the state's tokens and never compute the effect themselves, with one exception: on a tone a host gives it (BTN-45), whose color is known only where the button is drawn, the button lays the tone over its container at the system's state-layer opacities, as a carousel lays its state layer over a product's image. A system that draws no state layer sets those opacities to 0.
  • A narrow icon-only button is drawn narrower than the minimum target; its hit area is not (BTN-21). Leave room for the target beside it, or put narrow buttons in a toolbar or a group that spaces them.
  • The outline of an outlined button is the hairline of the default system (the divider's color), which is far below 3:1 against the surface. That is within WCAG 1.4.11: the edge is not what identifies the control. Its label is text held to 4.5:1 and its icon to the same ratio, a toggle that is on is shown by its fill, and focus by the focus ring. A product whose outlined buttons must show a 3:1 edge sets comp.button.outlined.outline.color to sys.color.outline in a layer.
  • A button with a destination is a link: the link role, the platform's own link, Enter and not Space. It is not a button that navigates from a click handler: on the web such a button has no address to copy, cannot be opened in a new tab and is announced as something that acts on the page. The look does not say which of the two it is, and need not: sighted people tell a destination from an action by its label, as they do in a toolbar.
  • Tanrei draws no visited state for a link button: it is a control whose color says its emphasis and its state, and the web restricts what a visited link may change to colors a page cannot read back. Visited links are a matter for links in running text.
  • A link that opens a new context says so twice: the open_in_new mark for people who see it, and targetLabel for people who do not (WCAG 1.1.1: the mark carries information, so it needs a text alternative; technique G201 advises warning before a new window). The words are the product's.
  • An action repeated in every row of a list or a table is drawn with one word and named for its row: label is the whole name ("Reset: Spacing"), opening with the drawn word where the language allows. Six buttons that are all named "Reset" cannot be told apart in the list of controls a screen reader offers, and a description does not appear there. The name must contain the drawn text (WCAG 2.5.3, label in name): a person who uses speech input says the word on the button. A debug build reports a name that does not; it never repairs one, because joining two texts into a phrase is a decision about a language, and the words are the product's.
  • On the web a Button of type submit or reset is its form's button as the platform's own is: the form validates, fires its submit or reset event, and the pressed button's name and value are sent with it. Enter in a field presses the form's first submit button, and a disabled one stops it. The default type is button, not the platform's submit: a button put in a form for another purpose must never send it by accident (BTN-27, BTN-33).
  • A button can keep a form from being sent twice: while busy it is shown and focusable but refuses every press, emits no click and therefore submits nothing (BTN-27 follows click), and it stops Enter in a field as a disabled default button does (BTN-37, BTN-32). Prefer it to disabling the button while the first submission is on its way: a disabled button drops the person's focus, and says nothing of why.
  • Where script does not run, or before the element is defined, a web Button is its text and nothing else, and submits nothing; so is every Tanrei field, so a form made of them cannot be filled either. A form that must be sent without script is made of the platform's own controls, with the platform's own submit button.
  • The native platforms have no form element, and the Button has no type there (BTN-34): which button the Return key presses is the app's to say, in the platform's idiom.
  • A disabled button leaves the focus order, so a keyboard or screen-reader user may never learn it is there or why it does nothing. Where a person can make the action available (fill in the message, choose a file), keep it reachable with focusableWhenDisabled and say why in a tooltip or a description; where the action simply does not apply, leave it out of the focus order (BTN-38).
  • destructive adds a color, never a meaning (WCAG 1.4.1, use of color): the error color tells people who see it that the action deletes or discards something, and nothing tells a screen reader user so but the label. So the label names the action and what it acts on ("Delete project", not "OK"), and a dialog that confirms it says what will be lost in its headline and supporting text. The web has no role or state for a destructive action, so none is invented; SwiftUI's destructive role is given where it exists, and the platform decides what it does with it.
  • Disabled, a destructive button looks like any disabled button of its variant: the error color is a warning about what a press would do, and a button that cannot be pressed does nothing.
  • A tone keeps a button readable on a colored surface its host draws (a status callout, an inverse snackbar): the button's content takes the surface's own content color, which the host holds to the contrast its own text needs, and its container is a translucent layer of that color rather than a color chosen against the page. The layer lowers the contrast between the content and what is under it a little (at 16 % in the default system, about a sixth of the way from the surface toward the content color): a host's content color must still hold 3:1 against that blend for an icon-only button (WCAG 1.4.11) and 4.5:1 for a labelled one (WCAG 1.4.3).

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

组件负责的部分

  • 在每个平台上,它都向辅助技术暴露按钮角色和禁用状态。有 href 时,它暴露的是链接角色,并且只有 Enter 能激活它。
  • 切换按钮是带有“已按下”状态的按钮,不是复选框,也不是开关;它的名称不随状态改变。
  • 会打开新上下文的链接用两种方式说明这一点:对看得见的用户是 open_in_new 标记,对看不见的用户是作为链接描述的 targetLabel。标记本身对辅助技术隐藏。
  • 在画出的标签之外还给了 label 时,它在一个控件上只暴露一个名称,也就是 label。画出的文字不会被再读一遍。
  • 在 Web 上,在字段中按 Enter 会按下表单的默认按钮,禁用或忙碌的默认按钮会拦住这个按键,位于禁用的 fieldset 之内的按钮也随之禁用。
  • 忙碌的按钮,以及带 focusable-when-disabled 的禁用按钮,都留在焦点顺序中并保持焦点。它们告诉辅助技术现在不能激活,忙碌的按钮还会朗读一次 busy-label。
  • 即使是 small 尺寸和 narrow 宽度,它的点击区域也达到最小目标,而按钮不必画得更大。
  • 获得键盘焦点时,它显示“先留间隙、再画圆环”的焦点指示,不带动画。
  • 它的颜色来自成对的角色,这些角色对的对比度在每个对比度级别下都经过校验。outlined 按钮的轮廓是默认系统的细线,在标准对比度下有意画得很淡:让人认出这个控件的是标签和图标,而不是它的边线。在中、高对比度下,这条细线会提高到 3:1 和 4.5:1。需要更醒目边线的产品,可以在层中把 comp.button.outlined.outline.color 设为 sys.color.outline。
  • 文本放大时,容器随之变大,标签绝不截断。
  • destructive 只改变颜色。角色、名称、状态和按键与不带它的同一个按钮相同,因为 Web 上没有表示操作具有破坏性的角色或状态。在 SwiftUI 上,它就是平台自己的破坏性角色(destructive role)。

需要你做的事

  • 写出脱离上下文也能看懂的标签。在项目名称旁边,“删除项目”比“删除”更好,因为屏幕阅读器用户可能只听到这个按钮。
  • 如果操作会启动一个耗时较长的流程,请设置 busy 和 busy-label,并在流程结束时朗读结果。仅仅禁用按钮,对屏幕阅读器用户来说什么也没有传达,还会让他们失去焦点。
  • 不要只靠颜色来说明操作具有破坏性。destructive 会用错误色来绘制它,但看不到或分辨不出这种颜色的人,只能从标签得知。写“删除项目”,而不是“确定”,并在确认它的对话框中说明会失去什么。
  • 按链接的去向为链接按钮命名:写“查看发票”,不要写“点击这里”。target 为 new 时,写上 targetLabel。
  • 几个按钮画着同一个词时,给每个按钮一个 label,它包含这个词,并说明按钮作用于什么(参见比标签更长的名称)。
  • 表单的提交按钮被禁用时,请在页面上说明还缺少什么。禁用的按钮也会拦住 Enter,而它本身什么也没有解释。
  • 用 label 为每个纯图标按钮命名。工具提示可以向使用指针的用户显示同样的文字,但它永远不能代替 label。