设计师到工程师的交接失败了无障碍性
对50个Figma文件的研究
我们在获得许可和全程匿名的条件下,获取了28个产品团队的50个生产级Figma文件的只读访问权限,然后用一个问题审阅每一个文件:当工程师打开这个文件开始实现时,设计师已经做出了哪些无障碍决策——哪些还留给工程师在周五下午四点自行发挥?答案,文件接着文件,是大多数决策仍然在下午四点被自行发挥。
1. 我们如何审计这50个文件
样本来自SaaS、零售、金融科技、公共部门和教育科技五个行业的28个产品团队的50个Figma文件。我们以非署名方式协商获得只读访问权限:本文中不识别任何品牌、团队或设计师。这些文件的选取旨在反映工程师在交接时实际收到的内容——而非来自作品集网站的精心打磨的案例研究——因此我们要求每个团队分享最近一个功能从中交付的文件,而非他们最引以为傲的文件。五十个文件中有十二个来自拥有专属设计系统实践的团队;其余三十八个来自产品级文件,这些文件引入了系统库或直接在文件内实现了自己的组件。
我们审阅每个文件时寻找五个无障碍属性:每个交互组件上的焦点状态设计、每张图像或非装饰性图标上的替代文本注释、跨布局的阅读顺序文档、任何带有动画或过渡效果元素的动效偏好处理,以及同时发布到亮色和暗色主题的任何组件的暗色模式对比度规格。对于每个属性,文件只有在工程师无需自行发明答案即可根据设计实现的情况下才得分”已记录”。“写在便利贴上的提及”不算。“在单一悬停状态中指定的十六进制颜色”不算。标准是:该决策是否以工程师可以直接执行的形式存在于文件中,无需询问?
核心发现是,按此标准,交接文件遗漏无障碍决策的频率远高于包含它们的频率。焦点状态设计出现在整个语料库约40%的交互组件中。替代文本注释出现在约22%需要它们的图像中。阅读顺序在16%的文件中得到明确记录。动效偏好在10%的文件中得到处理。对于同时发布两种主题的31个文件,暗色模式对比度在30%的组件中得到指定。差距不在某一个属性中,而在全部五个属性中,工程师被留下来逐一判断。
我们使用”工程师阅读并实现”的标准。焦点状态只有在文件以工程师可以映射到CSS令牌的形式显示视觉规格——轮廓颜色、宽度、偏移量、与聚焦元素背景的对比度——时才算已记录。附近Slack消息里写着”用品牌蓝色”不算,因为Slack消息无法在交接中存活下来。文件必须独立承载该决策。
“交接之所以失败,不是因为设计师不关心无障碍性,而是因为文件格式将无障碍性视为注释,而非每个组件的一等属性。”
2. 焦点状态设计:60%的缺口
在整个语料库中涉及的约1,800个交互组件中——按钮、链接、输入框、复选框、开关、标签页、组合框、菜单项、作为按钮的卡片,任何键盘用户可以到达的元素——约40%交付了设计好的焦点状态。另外60%交付了默认状态、激活状态和悬停状态,然后停止了。实现组件的工程师在实现时自行选择一个焦点轮廓,通常是复制浏览器默认值,通常不检查该默认值在文件发布的亮色和暗色主题中是否都与组件表面保持3:1对比度。
“没有焦点状态设计”在实践中是什么样子?它看起来像画布上有三个变体的按钮组件——静止、悬停、按下——却没有第四个变体。它看起来像带有样式化边框的输入字段,却没有焦点状态的第二种边框样式。它看起来像只有在静止变体上有焦点环的复选框原语,工程师被留下来猜测同样的环是否应该出现在勾选或不确定变体上。这种模式在组件中、跨团队、跨行业重复出现。它是语料库中最大的单一无障碍差距,也是最容易设计进去的。
那些确实把焦点状态设计好的团队,有两者之一作为支撑。第一个是明确的设计系统规则:每个交互组件必须交付一个名称以focus-开头的变体,在该变体存在之前组件不会发布到库中。第二个是Figma组件属性,名为state,将focus、focus-visible和focus-within作为枚举值,使文件的组件浏览器在视觉上呈现缺失的变体。没有这两种脚手架之一的团队,在约十次中有九次将焦点状态留给了工程师。
按钮组件,四个变体:state=default、state=hover、state=pressed、state=focus-visible。focus-visible变体显示2px轮廓、2px偏移、颜色令牌—focus-ring(该令牌本身映射到在两种主题中与按钮表面均通过3:1的十六进制颜色)。工程师阅读检查面板并复制令牌引用;无需任何发明。
同样的按钮组件,三个变体:default、hover、pressed。画布上没有焦点变体。设计师的便利贴写着”使用系统焦点环”。工程师搜索设计系统库,找到两个候选焦点环(一个来自按钮,一个来自输入框,宽度略有不同),选择其中一个,交付,三周后QA审查标记出所选环在禁用但仍可聚焦的次级按钮表面上低于3:1。
当焦点状态不在文件中时,工程师往往交付浏览器默认值——而浏览器默认值被同一位工程师六个月前为了清除另一个审查意见而添加的大多数CSS重置中的全局*:focus { outline: none }所覆盖。结果是,组件在Figma预览中看起来正常,在禁用了重置的开发环境中看起来正常,交付时却完全没有可见的焦点指示器。
3. 替代文本注释:大多数为空
在语料库中包含内容图像的文件——产品摄影、英雄插图、仅含图标的按钮、信息图表——78%在图像图层上没有替代文本注释。图像被放置、调整大小并设置样式;工程师应该放在渲染后的<img>上的文本等价物不在文件中。五十个文件中有八个在部分图像上有替代文本但不是全部,通常是英雄插图有注释而正文内容图像为空白。三个文件对每张图像都有替代文本。工程师在50个文件中的47个中,被期望自行编写替代文本——在实践中往往从文件名、图注或适合视觉节奏的任何字符串中继承。
这一差距在结构上源于Figma图像原语。图像填充或图像图层上没有原生”alt”属性;替代文本必须以图层名称、评论、便利贴、单独的规格文档或插件添加字段的形式承载。这些都不会通过检查面板默认传递,所以在检查视图中阅读文件的工程师即使设计师在别处写了替代文本也看不到。始终弥合这一差距的团队使用了三种变通方法之一:每个图像变体上有插件管理的替代文本字段、将图层名称作为替代文本的有据可查的约定,或随文件一起交付的以图像文件名为索引的替代文本电子表格。
仅含图标的按钮是这个故障模式中的子故障。在50个文件中的41个里,图标按钮——搜索放大镜、汉堡菜单、关闭X、分享箭头——没有无障碍名称注释,工程师只能从视觉上下文写出aria-label=“Search”,而没有确认”Search”是否是品牌声音中的正确词(是”Find”?是”Lookup”?还是因为按钮在其他地方标注的面板而什么都不是?)。图标命名恰恰是那种受益于设计师之笔的微文案决策,也恰恰是交接会丢失的那种。
每张图像要么是装饰性的(alt应为空,屏幕阅读器跳过它),要么是信息性的(alt承载视觉传达的信息)。这一选择是内容决策,属于设计师或文案,而非深夜猜测的工程师。一个对哪些图像是装饰性的只字未提的文件,交付的alt文本要么太多(每张图像都被详细描述,包括纯粹装饰性的图像),要么太少(英雄插图有描述,每张正文图像得到alt="",因为工程师保险起见)。
4. 阅读顺序、动效与暗色模式对比度
其余三个属性呈现出不同形态的失败。阅读顺序——屏幕阅读器叙述页面的顺序,在现代响应式布局中不再保证与视觉从上到下的顺序一致——在16%的文件中得到记录。记录存在时,通常是用插件在画布上添加的编号叠加层(1、2、3……)。其余84%让工程师从他们碰巧编写的DOM顺序映射阅读顺序,而在具有显式行列定位的CSS Grid布局上,这可能与视觉布局相差整整一列。
动效偏好最差。十分之一的文件提及了prefers-reduced-motion。其余90%指定了动画和过渡——模态框入场、手风琴展开、通知栏滑入、页面过渡——却没有指定当用户启用了减少动画时同一组件应该如何表现。工程师要么在实现时构建减少动画的情况(通常没有视觉参考),要么向所有人交付同样的动画,这是默认行为,而且对设置了该偏好的用户违反了2.3.3(交互动画)。
暗色模式对比度在同时发布两种主题的文件中,30%的组件有规格。另外70%指定了亮色主题对比度——通常在文件中有Stark或对比度检查器注释——然后用十六进制翻转调色板发布暗色主题,让工程师检查翻转后的颜色对是否在正文文字上仍然达到4.5:1、在UI组件上达到3:1。在31个双主题文件中,约五分之一至少有一个组件在暗色主题中低于对比度阈值,因为暗色表面和暗色文字都为亮色主题的对比度数学而调整,而非为暗色主题的。
矩阵追踪整个语料库中每个属性的”完成率”——文件中该属性按”工程师阅读并实现”标准记录的比例。各列按文件是否来自拥有专属设计系统实践的团队或直接实现组件的产品团队划分完成率;两列之间的差距是系统对无系统的差值。
| 无障碍属性 | 全部50个文件 | 设计系统团队(12个) | 产品团队(38个) | 系统与产品差值 |
|---|---|---|---|---|
| 焦点状态设计(交互组件) | 40% | 75% | 29% | +46pp |
| 替代文本注释(内容图像) | 22% | 50% | 13% | +37pp |
| 阅读顺序(画布级别) | 16% | 42% | 8% | +34pp |
| 动效偏好(动画元素) | 10% | 33% | 3% | +30pp |
| 暗色模式对比度(仅双主题文件,n=31) | 30% | 55% | 19% | +36pp |
”设计系统团队记录无障碍决策的比率约为产品团队的两倍——但即便是系统团队,在五个属性中大多数情况下也只在其中一个上达标。”
5. Stark和Able:参差不齐的采纳情况
在语料库中出现最频繁的两个插件是Stark和Able。两者都已成熟,都受到好评,都提供能够弥合上文记录的几个差距的功能。Stark添加了对比度检查器、焦点顺序叠加层、减少动画预览,以及图像图层上的替代文本注释字段。Able添加了颜色对比度检查器、视力模拟叠加层和触控目标检查器。任何一个插件,如果在文件中始终使用,都会将该文件从语料库的末尾四分之一中提升出来。
“始终使用”是关键词。在50个文件中,Stark在18个中被安装并可见地使用,Able在11个中。在使用了插件的文件中,它通常用于英雄组件和主要CTA——设计师打开插件时画布上最可能在视野内的组件——在其他地方则很少使用。六个文件对整个文件进行了Stark全局扫描;一个对整个文件进行了Able全局扫描。模式是:插件存在,设计师了解它们,它们被引入进行抽查,然后抽查在设计师打开插件时碰巧正在查看的组件处停止。
在插件使用方面结束审计的两个团队做了一件不同的事:他们在将文件分享给工程之前,将插件的审计功能作为发布关口步骤在文件的每一页上运行。审计在文件中运行,产生一份报告,在文件从”设计中”移至”工程就绪”之前,该报告必须为空(或其例外情况已记录)。这是”插件作为工作流程”而非”插件作为抽查”,这就是我们样本中80%覆盖率与20%覆盖率之间的差距。
插件提升了底线:对比度检查器捕捉明显的2.1:1故障,替代文本字段给设计师提供了可以输入的地方。如果插件只在三个组件上运行而非其余27个,这些都无济于事。修复方法是将插件纳入工作流程——发布关口步骤、交接前核查清单、不能在没有空插件报告的情况下合并的Figma分支——而非在设计师记得它存在的那一刻由其自行决定。
6. 交接核查清单与设计令牌契约
审计产生了一份核查清单和一份契约。核查清单是设计师在将文件分享给工程之前应该能够逐项勾选的内容。契约是随文件一起传递的设计令牌的形态,使工程师将Figma变量映射到CSS自定义属性时无需发明中间值。两者都刻意保持简短:核查清单上的每一项都是审计测量的属性,契约中的每个令牌都是在语料库中弥合差距的值。
每个交互组件交付一个state=focus-visible变体。
不是”系统有焦点环”,而是组件本身上名为focus-visible的变体,轮廓颜色、宽度和偏移量绑定到焦点环令牌。变体是工程师复制到实现中的内容;没有它,工程师就只能猜测。
每张内容图像在插件管理字段或有文档记录的图层名约定中有替代文本。
选择一个位置并强制执行。Stark替代文本字段、被视为替代文本的图层名,或附属电子表格——三者中的任何一种都有效,但前提是文件中的每张图像都使用同一种。仅含图标的按钮在同一位置也有无障碍名称注释,带有工程师应该放在aria-label中的确切字符串。
在任何DOM顺序与视觉顺序会发生偏差的页面上,阅读顺序须有文档记录。
最简单的文档是用插件添加的编号叠加层(Stark有这个功能,几个社区插件也有)。对于顺序明显是从上到下从左到右的页面,可以跳过叠加层;对于使用CSS Grid定位、具名区域或绝对定位的任何页面,叠加层是强制性的。
每个带有动画或过渡效果的元素在画布上有减少动画的变体。
第二个框架、第二个变体,或有文档记录的”无动画”版本。工程师不应该发明减少动画的情况——设计师应该指定模态框是交叉淡入而非滑入、通知栏是立即出现而非滑入、页面过渡是完全省略。
对于双主题文件,暗色主题的对比度须单独检查,而非从亮色主题推导。
暗色模式对比度有其自身的问题;翻转调色板是不够的。在暗色模式下对每个组件运行Stark或Able,而非只在亮色主题下。在变体的注释中记录对比度比率,使工程师可以确认实现与规格一致。
文件随令牌契约一起交付:每个Figma变量映射到其CSS自定义属性的平面列表。
契约是文件与代码库之间的桥梁。典型的契约如下表所示:每行命名一个Figma变量、工程师应绑定的CSS自定义属性、亮色主题的值、暗色主题的值,以及该令牌参与的WCAG标准。
| Figma变量 | CSS自定义属性 | 亮色值 | 暗色值 | 相关WCAG标准 |
|---|---|---|---|---|
| color/focus-ring | —focus-ring | #0B57D0 | #A8C7FA | 2.4.7, 1.4.11 |
| color/text/body | —text-body | #1F1F1F | #E3E3E3 | 1.4.3(表面上4.5:1) |
| color/surface/raised | —surface-raised | #FFFFFF | #1F1F1F | 1.4.11(与相邻元素3:1) |
| size/touch-target/min | —touch-target-min | 44px | 44px | 2.5.5, 2.5.8 |
| motion/duration/standard | —motion-standard | 200ms | 200ms | 2.3.3(减少动画时跳过) |
| motion/duration/reduced | —motion-reduced | 0ms | 0ms | 2.3.3 |
契约一旦存在,工程师的工作就变成机械性的:将CSS自定义属性绑定到Figma变量,交付实现,通过将渲染值与契约进行比较来审计。没有契约,每个绑定都是判断调用,判断调用积累成60%的差距。契约是将无障碍性从”工程师在交接时负责”转变为”系统在设计时负责”的单一产物。
结论:文件就是契约
50个文件的审计以一个简单的发现收尾。交接之所以使无障碍性失败,不是因为设计师不关心,也不是因为工程师不关心,而是因为文件——Figma文件,每个参与方都阅读的单一产物——没有将无障碍决策作为一等属性承载。焦点状态、替代文本、阅读顺序、动效偏好、暗色模式对比度:每一项都是设计决策,每一项都属于文件,每一项目前都在其他地方。在便利贴里,在Slack消息里,在单独的电子表格里,在工程师周五下午四点的脑子里。
修复方法不是一位英雄设计师或一位英雄工程师。而是团队层面的工作流程变革:每个交互组件交付焦点变体,每张图像在一个插件管理的位置承载替代文本,阅读顺序叠加在任何非平凡页面上,动画指定其减少动画的对应版本,暗色模式对比度独立于亮色主题单独检查,文件随令牌契约一起交付,命名实现所绑定的每个变量。这些步骤没有一个是新的,没有一个需要我们还没有的工具,任何将它们作为发布关口步骤采纳的团队将在一个发布周期内弥合我们测量的大部分差距。
更深层的发现是,设计系统团队已经以约两倍于产品团队的比率做到了这一点。设计系统团队提供的提升,恰恰是构建系统这门学科所施加的提升:组件被命名,属性被枚举,变体是可见的,令牌是明确的。将同样的学科带到产品级文件中——即便没有完整设计系统作为底层——可以弥合大部分交接差距。这不再是工具问题,而是工作流程选择。
“文件到达时应该已经做出了无障碍决策。任何其他情况,都是工程师在最糟糕的时刻、以最少的背景、在最紧迫的截止日期下发明这些决策。”