按钮组
按钮组把作用于同一对象、层级相同的相邻操作连接起来,同时保留每个操作独立的名称、焦点和原生行为。
安装 Terminal
npm install @a3s-lab/ui@0.4.1示例 index.html入口 main.js
<style>
@import "@a3s-lab/ui/a3s.css";
</style>
<section data-message-actions>
<div role="group" aria-label="消息操作" class="button-group">
<button
type="button"
class="btn"
data-variant="outline"
data-message-action
data-message-action-status="消息已归档。"
>
归档
</button>
<button
type="button"
class="btn"
data-variant="outline"
data-message-action
data-message-action-status="已请求稍后提醒选项。"
>
稍后提醒
</button>
<button
type="button"
class="btn"
data-variant="outline"
data-message-action
data-message-action-status="消息已标记为未读。"
>
标记未读
</button>
</div>
<output data-message-action-status aria-live="polite">
尚未选择消息操作。
</output>
</section>
<script type="module">
const region = document.querySelector("[data-message-actions]");
const status = region.querySelector("[data-message-action-status]");
region.addEventListener("click", (event) => {
const action = event.target.closest("[data-message-action]");
if (!action) return;
status.textContent = action.dataset.messageActionStatus;
});
</script>import "@a3s-lab/ui/a3s.css";
import "@a3s-lab/ui/all";
用法
导入 Tailwind、按钮组结构、按钮结构和一个视觉样式包。完整的 a3s.css 已经包含这些组件文件。
@import "tailwindcss" ;
@import "@a3s-lab/ui/base.css" ;
@import "@a3s-lab/ui/components/button-group.css" ;
@import "@a3s-lab/ui/components/button.css" ;
@import "@a3s-lab/ui/styles/a3s.css" ;
只有当控件作用于同一对象、处于同一层级,并且确实需要连续边界时才进行分组。根元素必须提供能说明共同任务的可访问名称。
< div role = "group" aria-label = "消息操作" class = "button-group" >
< button type = "button" class = "btn" data-variant = "outline" >归档</ button >
< button type = "button" class = "btn" data-variant = "outline" >稍后提醒</ button >
< button type = "button" class = "btn" data-variant = "outline" >标记未读</ button >
</ div >
参数
按钮组没有 JavaScript 控制器、方法、自定义事件、React Hook 或 Vue Composable。每个子控件负责自己的原生激活,宿主负责操作状态和结果。React 与 Vue 适配器输出同一个 div、role="group" 和 button-group 类,不增加包装层。
状态与变体
方向
短小的同级操作默认水平排列。只有命令描述空间顺序,或所属区域有意保持狭窄时才使用垂直组。同一组中不能混用不同按钮高度。
<div class="grid w-full max-w-lg gap-6 sm:grid-cols-2">
<section class="grid content-start gap-2">
<strong class="text-sm">修订历史</strong>
<div role="group" aria-label="修订历史操作" class="button-group">
<button type="button" class="btn" data-size="sm" data-variant="outline">
撤销
</button>
<button type="button" class="btn" data-size="sm" data-variant="outline">
重做
</button>
</div>
</section>
<section class="grid content-start gap-2">
<strong class="text-sm">对象顺序</strong>
<div
role="group"
aria-label="对象顺序操作"
class="button-group"
data-orientation="vertical"
>
<button type="button" class="btn" data-size="sm" data-variant="outline">
上移一层
</button>
<button type="button" class="btn" data-size="sm" data-variant="outline">
下移一层
</button>
</div>
</section>
</div>
拆分操作
拆分操作由一个可立即执行的命令和一个独立命名的菜单触发器组成。按钮组只负责连接两者的几何边界;下拉菜单 负责弹层焦点、键盘导航、碰撞处理和焦点恢复。
<section class="grid gap-3" data-button-group-split-demo="zh">
<div role="group" aria-label="发布操作" class="button-group">
<button
type="button"
class="btn"
onclick="this.closest('[data-button-group-split-demo]').querySelector('[data-button-group-split-status]').textContent = '已请求发布。'"
>
发布
</button>
<hr role="separator" aria-hidden="true" />
<div id="button-group-publish-menu-zh" class="dropdown-menu">
<button
type="button"
id="button-group-publish-trigger-zh"
class="btn"
data-size="icon"
aria-label="选择发布方式"
aria-haspopup="menu"
aria-controls="button-group-publish-menu-list-zh"
aria-expanded="false"
>
<svg
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m6 9 6 6 6-6" />
</svg>
</button>
<div
id="button-group-publish-popover-zh"
data-popover
aria-hidden="true"
data-align="end"
>
<div
role="menu"
id="button-group-publish-menu-list-zh"
aria-labelledby="button-group-publish-trigger-zh"
>
<div
id="button-group-publish-schedule-zh"
role="menuitem"
onclick="this.closest('[data-button-group-split-demo]').querySelector('[data-button-group-split-status]').textContent = '已安排定时发布。'"
>
安排定时发布
</div>
<div
id="button-group-publish-draft-zh"
role="menuitem"
onclick="this.closest('[data-button-group-split-demo]').querySelector('[data-button-group-split-status]').textContent = '已保存草稿,尚未发布。'"
>
保存为草稿
</div>
</div>
</div>
</div>
</div>
<output
class="text-sm text-muted-foreground"
data-button-group-split-status
aria-live="polite"
>
可以发布。
</output>
</section>
长标签与可用性
不可用的子操作仍应清晰显示名称,并使用原生禁用状态。原因必须持续显示在附近;不要禁用根元素,也不要只依赖 Tooltip。窄屏下,文字操作可以同步收缩和换行,不能制造页面级横向滚动。
请求安全审阅 部署到生产环境 保存稍后处理
安全审阅通过后才能部署到生产环境。
<section class="grid w-full max-w-sm gap-2">
<div
role="group"
aria-label="发布审阅操作"
class="button-group"
data-button-group-long-labels="zh"
>
<button type="button" class="btn" data-size="sm" data-variant="outline">
请求安全审阅
</button>
<button
type="button"
class="btn"
data-size="sm"
data-variant="outline"
disabled
aria-describedby="button-group-production-reason-zh"
>
部署到生产环境
</button>
<button type="button" class="btn" data-size="sm" data-variant="outline">
保存稍后处理
</button>
</div>
<p
id="button-group-production-reason-zh"
class="text-xs text-muted-foreground"
dir="auto"
>
安全审阅通过后才能部署到生产环境。
</p>
</section>
RTL
按钮组通过逻辑起始边和结束边遵循 DOM 顺序。无论书写方向如何,第一个操作都出现在行内起始位置。只翻转表达空间方向的图标;对象、状态、品牌、加号和删除图标保持原方向。
可访问性与职责边界
每个子控件保留独立的可访问名称、焦点指示、禁用状态和原生 Tab 停靠点。按钮组不会加入方向键漫游,也不会把多个操作转换成一个控件。互斥视图使用 Tabs 或分段选择组件,独立格式状态使用 Toggle Group,带前后缀操作的值使用输入组,页码导航使用 Pagination,多组命令共享一行时使用 Toolbar。
在 200% 缩放和 320px 宽度下,按钮组必须保持在所属区域内,不能出现双重边框、标签裁切或页面横向滚动。粗指针设备上的每个子控件仍需满足按钮的触控目标约束。整组不可用时,应逐个禁用子控件,让辅助技术获得每条命令的真实可用性。