样式聚焦

焦点指示器(通常用“焦点环”表示)用于标识网页上处于焦点状态的元素。对于无法或不想使用鼠标的用户,此指示器非常重要,因为它可替代鼠标指针。

如果浏览器的默认焦点指示器与您的设计相冲突,您可以使用 CSS 重新设置其样式。请务必牢记您的用户。

使用 :focus 始终显示焦点指示器

:focus 伪类会应用于处于焦点状态的元素,无论使用的是哪种输入法或设备(例如鼠标、键盘或触控笔)。

例如,以下 <div> 具有使其可聚焦的 tabindex,并为其 :focus 状态设置了自定义样式:

div[tabindex="0"]:focus {
  outline: 4px dashed orange;
}

无论您使用什么设备,<div> 在聚焦时看起来都一样。

遗憾的是,浏览器在应用焦点方面可能不一致。元素是否获得焦点可能取决于浏览器和操作系统。

例如,以下 <button> 为其 :focus 状态设置了自定义 CSS。

button:focus {
  outline: 4px dashed orange;
}

如果您在 macOS 上的 Chrome 中使用鼠标点击 <button>,应该会看到其自定义焦点样式。不过,如果您在 macOS 上的 Safari 中点击 <button>,则不会看到自定义焦点样式。这是因为在 Safari 中,当您点击元素时,该元素不会获得焦点。

焦点行为不一致。在不同设备上使用不同输入测试网页,确保焦点样式能让用户接受。

使用 :focus-visible 有选择地显示焦点指示器

当元素获得焦点,并且浏览器(通过启发式方法)确定显示焦点指示器对用户有益时,系统会应用 :focus-visible 伪类。具体来说,如果最近一次用户互动是通过键盘进行的,并且按键操作不包含 meta、ALTOPTIONCONTROL 键,则 :focus-visible 会匹配。

以下示例中的按钮会有选择性地显示焦点指示标志。如果您使用鼠标点击,结果会与您先使用键盘切换到该元素的结果不同。

button:focus-visible {
  outline: 4px dashed orange;
}

使用 :focus-within 设置聚焦元素的父元素的样式

当元素本身或其子元素获得焦点时,:focus-within 伪类会应用于该元素。它可用于突出显示网页的某个区域,以吸引用户注意该区域。

例如,当表单本身被选中时,以及当表单的任何单选按钮被选中时,表单都会获得焦点。

form:focus-within {
  background: #ffecb3;
}

何时显示焦点指示器

一个很好的问题是问问自己:“如果您在使用移动设备时点击此控件,您是否希望它显示键盘?”

  • 如果为“是”:无论输入设备是什么,控件都应始终显示焦点指示器。例如,<input type="text"> 元素几乎总是如此。用户需要使用键盘向元素发送输入内容,无论输入元素如何获得焦点。
  • 如果为“否”:控件可以选择性地显示焦点指示器。例如,当用户使用鼠标或在触摸屏上点击 <button> 时,操作即完成。焦点指示器可能是不必要的。不过,如果用户使用键盘进行导航,最好显示焦点指示器,以便用户决定是否要使用 ENTERSPACE 键激活控件。

避免使用 outline: none

坦白地说,浏览器决定何时绘制焦点指示器的方式非常令人困惑。使用 CSS 更改 <button> 元素的外观或为元素提供 tabindex 会触发浏览器的默认焦点环行为。

有时,开发者会使用 CSS 移除焦点指示器:

/* Don't do this!!! */
:focus {
  outline: none;
}

解决此问题的更好方法是结合使用 :focus:focus-visible polyfill。第一个代码块展示了填充区的运作方式,下面的示例应用展示了如何使用填充区来更改按钮上的焦点指示器。

/*
  This hides the focus indicator if the element receives focus with a
  mouse, but it still shows up on keyboard focus.
*/
.js-focus-visible :focus:not(.focus-visible) {
  outline: none;
}

/*
  Optionally: Define a strong focus indicator for keyboard focus.
  If you choose to skip this step, then the browser's default focus
  indicator will be displayed instead.
*/
.js-focus-visible .focus-visible {
  ...
}