纯粹的CSS自定义复选框样式

5,912 阅读7分钟

我们将用纯CSS创建自定义的、跨浏览器的、可主题的、可扩展的复选框,具体如下:

  • currentColor 主题性,包括SVG的主题性
  • em 相对尺寸的单位
  • 使用SVG来表示:checked
  • 用CSS网格布局来对齐输入和标签

这里的许多概念与第18集的自定义样式的单选按钮重叠,此外还包括为:checked 状态使用SVG,并包括:disabled 状态的样式。

复选框HTML

单选按钮的文章中,我们探讨了标记输入字段的两种有效方法。与此类似,我们将选择标签包住输入的方法。

对于我们的复选框,我们将利用内联SVG来实现我们的自定义控件,所以这里是我们测试未选中和选中状态的基本HTML。

<label class="form-control">
  <input type="checkbox" name="checkbox" />
  Checkbox
</label>

<label class="form-control">
  <input type="checkbox" name="checkbox-checked" checked />
  Checkbox - checked
</label>

注意aria-hidden="true"focusable="false" ,因为我们要把这个SVG当作一个装饰性的图标。在这种情况下,底层的复选框仍然可用,以提供可访问性的选中状态的语义。

原生复选框的常见问题

与单选按钮一样,复选框的外观在不同的浏览器中也有差异。

下面是Chrome、Firefox和Safari的基本样式(按从左到右的顺序):

default checkboxes in Chrome, Firefox, and Safari

也和单选按钮一样,复选框并没有随着font-size

我们的解决方案将实现以下目标:

  • 与提供给标签的font-size 缩放
  • 获得与提供给标签的颜色相同的颜色,以方便主题的实现
  • 实现一致的、跨浏览器的设计风格,包括:focus 状态
  • 保持键盘的可访问性

我们的样式将从用于单选按钮的相同变量和重置开始

标签样式

我们的标签使用的是.checkbox 。我们在这里要包含的基本样式是font-sizecolor 。回顾一下前面的内容,font-size 还不会对复选框输入的视觉尺寸产生影响。

我们使用一个异常大的font-size ,只是为了强调教程演示中的视觉变化。

我们的标签也是我们设计的布局容器,我们将把它设置为使用CSS网格布局,以利用网格间隙的优势。

.form-control {
  font-family: system-ui, sans-serif;
  font-size: 2rem;
  font-weight: bold;
  line-height: 1.1;
}

这是我们在Chrome浏览器中捕捉到的进度,检查器显示了网格线。

base styles applied to the label

由于我们将SVG的stroke 颜色定义为currentColor ,所以它也接受了rebeccapurple 的值。

.form-control {
  font-family: system-ui, sans-serif;
  font-size: 2rem;
  font-weight: bold;
  line-height: 1.1;
  display: grid;
  grid-template-columns: 1em auto;
  gap: 0.5em;
}

自定义复选框样式#

为了做好准备,我们已经将我们的input 包裹在一个span ,其类别为checkbox__input 。然后,我们还添加了一个span ,作为input 的兄弟姐妹,其类别为checkbox__control 。控制跨度也包含了复选标记SVG。

这里的顺序很重要,我们将在为:checked:focus:disabled 样式时看到。

第1步:隐藏本地复选框

我们需要隐藏本地复选框,但在技术上保持它的可访问性,以实现适当的键盘交互,并保持对:checked,:focus 状态的访问。

为了达到这个目的,我们将使用opacity ,在视觉上隐藏它,并将其宽度和高度设置为1em 。我们将保留宽度和高度,以确保触摸设备的用户可以发现。

input[type="checkbox"] {
  /* Add if not using autoprefixer */
  -webkit-appearance: none;
  appearance: none;
  /* For iOS < 15 to remove gradient background */
  background-color: #fff;
  /* Not removed via appearance */
  margin: 0;
}

第2步:自定义无勾选复选框的样式

对于我们的自定义复选框,我们将把样式附加到span.checkbox__control ,它是input 后面的兄弟姐妹。

我们将把它定义为一个inline-grid 元素,使用em 来保持它与应用于labelfont-size 的相对大小。我们还使用em 来保持border-width 的相对外观。然后,我们使用border-radius 来柔和地圆角。

input[type="checkbox"] {
  appearance: none;
  background-color: #fff;
  margin: 0;
  font: inherit;
  color: currentColor;
  width: 1.15em;
  height: 1.15em;
  border: 0.15em solid currentColor;
  border-radius: 0.15em;
  transform: translateY(-0.075em);
}

.form-control + .form-control {
  margin-top: 1em;
}

这是我们在隐藏本地输入并为自定义复选框控件定义了这些基本样式后的进展:

progress of styling the custom checkbox control

好了,display: inline-grid 发挥了它的魔力,很好地包含了SVG复选标记,但是自定义控件与标签的对齐方式是怎么回事呢?

由于我们在复选框输入上保留了widthheight ,并且只用不透明度来隐藏它,所以它仍然占据了空间。

第3步:设计:checked 与未选中的状态#的样式

我们使用opacity: 0 ,使原生的复选框输入可用于键盘交互以及点击/点按交互。

它还保持了用CSS检测其:checked 状态的能力。

还记得我提到的顺序问题吗?由于我们的自定义控件在本地输入之后,我们可以使用相邻的兄弟姐妹组合--+ --当本地控件是:checked 🙌时,我们的自定义控件的样式。

然而--由于我们使用一个内嵌的SVG来显示:checked 指示器,我们实际上需要将其设置为SVG最初是隐藏的,只有在:checked 状态下才显示。

我们将添加一个transition ,以便在切换状态时提供一个动画效果,并使用transform: scale(0) 来进行初始隐藏。

input[type="checkbox"] {
  /* ...existing styles */

  display: grid;
  place-content: center;
}

接下来,我们需要添加:checked 样式,将其重新放大到视图中。

input[type="checkbox"]::before {
  content: "";
  width: 0.65em;
  height: 0.65em;
  transform: scale(0);
  transition: 120ms transform ease-in-out;
  box-shadow: inset 1em 1em var(--form-control-color);
}
input[type="checkbox"] {
  /* ...existing styles */
  display: grid;
  place-content: center;
}

input[type="checkbox"]::before {
  content: "";
  width: 0.65em;
  height: 0.65em;
  transform: scale(0);
  transition: 120ms transform ease-in-out;
  box-shadow: inset 1em 1em var(--form-control-color);
}

input[type="checkbox"]:checked::before {
  transform: scale(1);
}

这里有一个动画的:checked 互动的演示:

gif of the checkbox being checked and unchecked

input[type="checkbox"]::before {
  /* ...existing styles */

  /* Windows High Contrast Mode */
  background-color: CanvasText;
}
input[type="checkbox"]::before {
  /* ...existing styles */

  transform-origin: bottom left;
  clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}

第四步::focus 状态

对于:focus 状态,我们将使用与单选按钮相同的双box-shadow 技术。这让我们可以利用currentColor ,但确保基本的自定义复选框和:focus 样式之间的区别。

input[type="checkbox"]:focus {
  outline: max(2px, 0.15em) solid currentColor;
  outline-offset: max(2px, 0.15em);
}

box-shadow定义的顺序意味着我们首先要创建一个薄的白色边框的外观,它出现在一个羽毛状的阴影上面,这个阴影的值来自currentColor

:root {
  --form-control-disabled: #959495;
}

input[type="checkbox"]:disabled {
  --form-control-color: var(--form-control-disabled);

  color: var(--form-control-disabled);
  cursor: not-allowed;
}
.form-control--disabled {
  color: var(--form-control-disabled);
  cursor: not-allowed;
}

下面是一个关于:focus 的外观的演示。

gif of the checkbox focus state

第5步::disabled 复选框的样式#

在单选按钮的教程中,我们错过了一个步骤,那就是为:disabled 状态设计样式。

这将遵循与之前状态类似的模式,这里的变化主要是将颜色更新为灰色。由于我们所有的颜色都与currentColor 值相关,这对自定义控件来说是一个简单的更新。

:root {
  --form-control-disabled: #959495;
}

input[type="checkbox"]:disabled {
  --form-control-color: var(--form-control-disabled);

  color: var(--form-control-disabled);
  cursor: not-allowed;
}

但我们遇到了一个障碍。由于标签是父元素,我们目前没有办法在CSS中单独根据:disabled 的状态对其进行样式设计。

对于一个只用CSS的解决方案,我们需要在知道复选框被禁用时为标签创建一个额外的类。由于这种状态不能被用户改变,这通常是一个可以接受的额外步骤。

我们将创建.checkbox--disabled 的类,添加到HTML标签元素中。

.form-control--disabled {
  color: var(--form-control-disabled);
  cursor: not-allowed;
}