One keyboard-focus colour for every control
所有控件共用一个键盘焦点颜色
Different explicit focus colours and browser-default outlines do not provide a consistent keyboard-focus cue. Browser defaults can also depend on the operating-system accent. Focus feedback has separate interaction and geometry constraints. A browser's `:focus-visible` heuristic can reveal a pointer-focused button after a key that does not move focus, while editable text controls need focus feedback on click. Contain
Source compatibility: Chinese title uses a compatible non-canonical header
English
Problem
Different explicit focus colours and browser-default outlines do not provide a consistent keyboard-focus cue. Browser defaults can also depend on the operating-system accent.
Focus feedback has separate interaction and geometry constraints. A browser's :focus-visible heuristic can reveal a pointer-focused button after a key that does not move focus, while editable text controls need focus feedback on click. Containers that clip their contents can hide an outer ring.
Decision
The theme's focus stylesheet owns the shared ring colour and standard width. Input modality distinguishes tooltip input from keyboard focus navigation.
One colour. Global and component :focus-visible outlines and focus-ring shadows read --dsw-focus-ring-color, falling back to --dsw-alias-state-business-primary. The global fallback names the colour and the standard width but never the style, so it cannot create an outline where the component disables one. Naming the width is what keeps a control with no ring of its own at the standard geometry instead of Chromium's auto 1px.
Context-owned geometry. --dsw-focus-ring-width carries the standard 2px width. Dense tables and toolbars may keep 1px so the ring does not dominate compact content. Components own their offsets and use an inset ring where an outer ring would be clipped.
Pointer focus does not imply keyboard navigation. In pointer modality, :focus-visible:not(:read-write) controls use a transparent ring colour. Descendant and pseudo-element rings inherit it. Only focus-ring paint is suppressed: selected-state borders and elevation shadows remain, and editable text controls keep their own focus feedback on click. DOM focus stays on the control.
Tooltips follow the last input and treat any key as keyboard input. Rings instead resume keyboard styling on non-composing navigation keys, or on focus reaching a different control after a non-composing key. Refocusing the same control is not navigation; pointer input, composition keys, and window blur clear the pending key.
Alternatives considered
Rewrite only the component declarations. Explicit rings would agree, but controls using a browser-default outline would retain its colour. Requiring every control to declare its own ring would also leave new controls inconsistent until their authors add that rule.
Unify width and offset globally. Positive offsets can be clipped by a scrolling ancestor, while a uniform thick ring can overwhelm dense content. Neither colour consistency nor input modality requires identical geometry.
Suppress by moving focus instead of by style. Calling blur() on a pointer-focused button would hide its ring but also remove the target for subsequent Enter or Space activation.
Clear all shadows on pointer focus. A box-shadow can contain elevation or selected-state paint as well as a focus ring. Suppressing its ring colour preserves those independent cues; exempting :read-write preserves text-entry feedback.
Replace every outline: none with a shared ring. Some controls use an inset shadow or container-level focus treatment instead; some containers deliberately show no ring. Replacing those declarations would change their focus treatment, not merely its colour.
Give tooltips and rings the same modality rule. A key can justify a tooltip on subsequent programmatic focus without justifying a ring on the same pointer-focused button. Keeping separate answers preserves both behaviours.
Consequences
Focus rings share a theme colour while components retain their geometry and non-focus state cues. Ring visibility changes without changing the keyboard's active target. These rules do not guarantee identical browser rendering or eliminate component-specific clipping.
Stylesheet-order checks, focus-style checks, and input-modality tests cover stylesheet declarations and modality transitions. Static declarations and synthetic events do not establish rendered ring visibility or cross-platform behaviour.
中文
问题
不同的显式焦点颜色与浏览器默认轮廓无法提供一致的键盘焦点提示。浏览器默认值还可能取决于操作系统强调色。
焦点反馈另有交互与几何约束。浏览器的 :focus-visible 启发式可能在不移动焦点的按键之后显出指针聚焦按钮的环,而可编辑文本控件在点击时需要焦点反馈。裁切内容的容器可能隐藏外扩环。
决策
主题焦点样式表 拥有共享焦点环颜色与标准宽度。输入模态 区分 tooltip 输入与键盘焦点导航。
一个颜色。 全局与组件的 :focus-visible 轮廓和焦点环阴影读取 --dsw-focus-ring-color,回退到 --dsw-alias-state-business-primary。全局兜底声明颜色与标准宽度、但不声明样式,因此组件禁用轮廓时它不会创建轮廓。声明宽度可让没有自有焦点环的控件保持标准几何,而不是落到 Chromium 的 auto 1px。
场景决定几何。 --dsw-focus-ring-width 提供标准的 2px 宽度。密集表格与工具栏可以保留 1px,避免焦点环压过紧凑内容。组件拥有自己的 offset,并在外扩环会被裁切时使用内嵌环。
指针焦点不等于键盘导航。 指针模态下,:focus-visible:not(:read-write) 控件使用透明的焦点环颜色。后代与伪元素上的环继承该值。只抑制焦点环绘制:选中态边框与 elevation 阴影保留,可编辑文本控件在点击时保留自身焦点反馈。DOM 焦点仍留在控件上。
Tooltip 跟随最近一次输入,将任意按键视为键盘输入。焦点环则在非组合输入的导航键到达时,或非组合输入按键之后焦点到达不同控件时,恢复键盘样式。重新聚焦同一控件不算导航;指针输入、组合输入按键和 window blur 会清除待处理按键。
考虑过的其他做法
只重写组件声明。 显式焦点环会一致,但使用浏览器默认轮廓的控件仍会保留其颜色。要求每个控件自行声明焦点环,也会让新控件在作者补上规则之前保持不一致。
全局统一宽度与 offset。 正 offset 可能被滚动祖先裁切,统一的粗环则可能压过密集内容。颜色一致性和输入模态都不要求相同的几何。
用移动焦点取代样式抑制。 对指针聚焦的按钮调用 blur() 可以隐藏焦点环,但也会移除后续 Enter 或 Space 激活的目标。
指针聚焦时清除所有阴影。 一个 box-shadow 除了焦点环,还可能包含 elevation 或选中态绘制。抑制其焦点环颜色可保留这些独立提示;豁免 :read-write 可保留文本输入反馈。
把所有 outline: none 改为共享焦点环。 一些控件改用内嵌阴影或容器级焦点处理;一些容器则刻意不显示焦点环。替换这些声明会改变焦点处理方式,而不只是颜色。
让 tooltip 与焦点环共用同一条模态规则。 按键可以让后续程序化聚焦显示 tooltip,却不一定需要在同一个指针聚焦按钮上显示焦点环。分别记录两个答案可保留两种行为。
影响
焦点环共享主题颜色,组件保留自己的几何与非焦点状态提示。焦点环可见性的变化不改变键盘的活动目标。这些规则不保证浏览器渲染完全相同,也不会消除组件特有的裁切。
样式表顺序检查、焦点样式检查与输入模态测试覆盖样式声明和模态转换。静态声明与合成事件不能确认焦点环的实际渲染可见性或跨平台行为。