Skip to content

InlineCreateRelated 的关闭按钮是纯图标且无可访问名:X 图标被 lucide 默认 aria-hidden,按钮名计算为空串 #3411

Closed

Description

@yinlianghui

实施 #3381(PR #3410,给 Link 页签搜索框补真 label)时,在同一文件里核到的相邻缺陷。#3381 的范围栅栏只覆盖搜索框的可访问名,不在该 PR 内修,单独立此单。

同族第四例,但类别又不同:#3299 是「required 状态没进 a11y 树」;#3341 是「label 存在但没关联上」;#3381 是「输入框根本没有 label」;本单是「按钮完全没有可访问名」——一个纯图标按钮,连兜底都没有(placeholder 至少还能在真实浏览器里兜一下,图标按钮连这个都没有)。

现象

packages/plugin-detail/src/InlineCreateRelated.tsx,卡片头部的关闭按钮(origin/main 2409e1d 上约 L189-L196,PR #3410 之后行号不变):

Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => setIsOpen(false)}
  X className="h-3.5 w-3.5" /
/Button

(为避开 GitHub 正文的标签清洗,上面省略了尖括号。)

该按钮没有文本子节点,也没有 aria-label / aria-labelledby / title。唯一的子节点是 lucide 的 X 图标,而 lucide-react 1.25.0 对「无 children 且无 a11y prop」的图标默认加 aria-hidden="true"(dist/cjs/lucide-react.js:92:...!children && !hasA11yProp(rest) && { "aria-hidden": "true" })。图标被排除出 a11y 树之后,按钮的可访问名没有任何来源,计算结果是空串。

屏幕阅读器用户听到的是一个「按钮」,没有名字;这也是 WCAG 4.1.2 / 2.4.6 的直接违反项。

复核方式(在 PR #3410 的 worktree 里实测)

临时探针断言「卡片展开后不存在无文本内容的按钮」:

const iconOnly = screen.getAllByRole('button').filter((b) => !b.textContent?.trim());
expect(iconOnly).toHaveLength(0);

结果:

AssertionError: expected [ button …(1) ] to have a length of +0 but got 1

即展开态里恰好有一个纯图标按钮(就是这个 X)。探针已删除,未进 PR #3410。

顺带核过同文件的其余按钮,都有文本因而有名字:折叠态的 New {objectName} / Link Existing、页签触发器、Cancel / Create、以及搜索结果行按钮。所以本单只涉及这一个关闭按钮。

建议修法

给按钮一个可访问名。与本文件已确立的形状一致的做法是 aria-label——注意这里和 #3381 的裁定不冲突:#3381 否决 aria-label 是因为那里存在可见文案(placeholder)、用 aria-label 会让可见文本与可访问名解耦;而这里没有任何可见文案,aria-label 是图标按钮的标准解法(另一种是 sr-only 文本子节点,效果等价)。

i18n:本文件目前整体是英文硬编码(未消费 useDetailTranslation),而 DETAIL_DEFAULT_TRANSLATIONS 里已有语义相近的键可考虑(如 detail.cancel);是否在本单顺带把该组件接入 i18n,属于范围问题,请 PM 裁定——纯补 aria-label(英文字面量,与同文件其余文案同一族)是最小改动。

验收建议

  • getByRole('button', { name: … }) 能拿到该关闭按钮;
  • toHaveAccessibleName 非空;
  • 反向验证:去掉新加的名字来源后该断言转红(注意别写成「不存在无文本按钮」这类会被其它改动带绿的间接断言)。

关联:#3381(PR #3410)、#3341(PR #3380)、#3299、#3290。

Activity

  1. self-assigned this
    on Aug 5, 2026
  2. yinlianghui commented on Aug 5, 2026

    @yinlianghui
    CollaboratorAuthor

    认领:objectui 分片 PM 滚动补位第 19 单(#3381/PR #3410 落地后同文件释放)
    会话:session_01GTRjn8xBqp75dk7kFupVRt
    分支:claude/issue-3411-close-button-name
    Worktree:objectui-issue-3411
    域:objectui 分片(plugin-detail)
    文件面:packages/plugin-detail/src/InlineCreateRelated.tsx + 相应测试 + .changeset/*.md(patch)。当前在飞 0。

    范围裁定(PM,否决窗口):最小修 —— aria-label 英文字面量(纯图标按钮的标准解法,与 #3381 裁定不冲突 —— 此处无可见文案无解耦问题;与同文件其余英文文案同族)。⛔ 本单不把组件接入 i18n(该文件整体英文硬编码是另一族问题,待与 #5506 同类项统一处理,别让一行 a11y 修复变成 i18n 清扫)。验收照正文:getByRole 按名命中 + toHaveAccessibleName 非空 + 直接反向断言(勿用会被其它改动带绿的间接形)。


    Generated by Claude Code

  3. yinlianghui commented on Aug 5, 2026

    @yinlianghui
    CollaboratorAuthor

    PM 验收:ACCEPT → PR #3413 已转 ready + auto-merge(CI 16/16 完成全绿),跟到 MERGED。

    落地内容(最小修):aria-label="Close" + 图标显式 aria-hidden(lucide 默认已隐藏,显式写出让意图本地化、不依赖图标库版本),形状与仓内其它 close 按钮(shadcn dialog/sheet、DashboardEditor)一致。反向验证修前 7/7 红且结构定位仍成功 —— 红的是名字不是元素移位。消费半径核实:该组件仅具名导出、无注册表消费方。刻意零衍生立单:同文件整体英文硬编码已由 PM 裁归 #5506 同族,dev 搜证无既有 tracker 后依纪律不另开孪生 —— 正确。

    至此 InlineCreateRelated 单文件的四类 a11y 缺陷全部闭环:#3299(状态)→ #3341(关联)→ #3381(无标签)→ #3411(无名字)。

    objectui 分片 PM session_01GTRjn8xBqp75dk7kFupVRt


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions