纯 CSS 自定义样式单选按钮

2025-06-04

纯 CSS 自定义样式单选按钮

这是系列文章的第十八篇,该系列文章探讨了过去 13 多年来我作为前端开发者所遇到的各种问题,并提出了相应的解决方案。访问ModernCSS.dev可以查看整个系列文章及其他资源

通过组合以下属性,我们可以用纯 CSS 创建自定义、跨浏览器、可主题化、可扩展的单选按钮:

  • currentColor主题能力
  • em相对尺寸单位
  • radial-gradient:before:checked指标相比
  • CSS 网格布局用于对齐输入和标签

注意:这些样式中有很多与自定义复选框样式一集重叠,您可能有兴趣接下来阅读!


现已推出:我的 Egghead 视频课程:无障碍跨浏览器 CSS 表单样式。你将学习如何将本教程中描述的技术提升到一个新的水平,创建一个可主题化的表单设计系统,并扩展到你的各个项目。

单选按钮 HTML

在 HTML 中,有两种适当的方法来布局单选按钮。

第一个将 包裹input在 内label。这隐式地将标签与其标记的输入关联起来,并且还增加了选择单选按钮的点击区域。

<label>
  <input type="radio" name="radio" />
  Radio label text
</label>
Enter fullscreen mode Exit fullscreen mode

第二种是让inputlabel成为兄弟并使用for设置为无线电值的属性id来创建关联。

<input type="radio" name="radio" id="radio1" />
<label for="radio1">Radio label text</label>
Enter fullscreen mode Exit fullscreen mode

我们的技术适用于任一设置,但我们将选择包装标签方法来防止包含额外的 div。

我们的演示的基本 HTML 包括类和两个单选按钮(测试:checked未选中状态所必需的),如下所示:

<label class="radio">
  <span class="radio__input">
    <input type="radio" name="radio">
    <span class="radio__control"></span>
  </span>
  <span class="radio__label">Radio 1</span>
</label>

<label class="radio">
  <span class="radio__input">
    <input type="radio" name="radio">
    <span class="radio__control"></span>
  </span>
  <span class="radio__label">Radio 2</span>
</label>
Enter fullscreen mode Exit fullscreen mode

对于单选按钮组,也需要提供相同的name属性。

Chrome 中的原生 HTML 元素显示方式如下:

Chrome 中的原生单选按钮

原生单选按钮的常见问题

导致开发人员寻求单选按钮自定义样式解决方案的主要问题是它们在不同的浏览器之间外观存在差异,而当包括移动浏览器时,这种差异还会增大。

例如,以下是 Mac 版 Firefox(左)、Chrome(中)和 Safari(右)上显示的单选按钮:

Firefox、Chrome、Safari 中的单选按钮

第二个问题是原生单选按钮无法仅根据字体大小进行缩放。以下是这些浏览器中再次出现的故障,顺序相同:

Firefox、Chrome、Safari 中的单选按钮没有文本缩放

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

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

主题变量和box-sizing重置

在我们的级联中,有两个基本 CSS 规则必须放在第一位。

首先,我们创建一个名为的自定义变量,--color我们将使用它作为一种简单的方法轻松地为单选按钮设置主题。

:root {
  --color: rebeccapurple;
}
Enter fullscreen mode Exit fullscreen mode

接下来,我们使用通用选择器重置box-sizing所使用的方法border-box。这意味着 padding 和 border 将包含在任何元素最终尺寸的计算中,而不是将计算尺寸增加到超出任何设定尺寸的范围。

*,
*:before,
*:after {
  box-sizing: border-box;
}
Enter fullscreen mode Exit fullscreen mode

标签样式

我们的标签使用了 类.radio。我们这里要包含的基本样式是font-sizecolor。回想一下之前提到的,font-size还不会对 radio 的视觉大小产生影响input

.radio {
  font-size: 2.25rem;
  color: var(--color);
}
Enter fullscreen mode Exit fullscreen mode

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

我们的标签也是我们设计的布局容器,我们将设置它以使用 CSS 网格布局来利用grid-gap

.radio {
  // ...existing styles

  display: grid;
  grid-template-columns: min-content auto;
  grid-gap: 0.5em;
}
Enter fullscreen mode Exit fullscreen mode

这是我们在 Chrome 中捕获的进度,其中 Inspector 显示网格线:

显示网格布局的无线电标签

自定义单选按钮样式

好的,这就是您来这里要看的部分!

为了做好准备,我们将我们的 包裹inputspan类中radio__input。然后,我们还添加了一个span作为 的兄弟inputradio__control

这里的顺序很重要,正如我们在为:checked和设计样式时所看到的那样:focus

步骤 1:隐藏原生广播输入

我们需要隐藏原生的单选按钮输入,但保持其在技术上可访问,以实现正确的键盘交互并保持对:focus状态的访问。

为了实现这一点,我们将使用opacity来在视觉上隐藏它,并将其设置为widthheight0减少其对元素流的影响。

.radio__input {

  input {
    opacity: 0;
    width: 0;
    height: 0;
  }

}
Enter fullscreen mode Exit fullscreen mode

您可能在过去见过更冗长的解决方案,但当我们添加自定义样式的控件时,我们就会明白为什么它有效。

步骤 2:自定义未选中的单选按钮样式

radio__control对于我们的自定义收音机,我们将样式附加到输入之后的同级类的范围。

我们将其定义为块元素,并使用 来调整em其大小,以使其与应用于标签的 保持相对关系font-size。我们还使用em的值border-width来保持相对外观。Good oleborder-radius: 50%通过将元素渲染为圆形来完成预期的外观。

.radio__control {
  display: block;
  width: 1em;
  height: 1em;
  border-radius: 50%;
  border: 0.1em solid currentColor;
}
Enter fullscreen mode Exit fullscreen mode

这是我们隐藏原生输入并为自定义单选按钮控件定义这些基本样式后的进展:

自定义单选控件样式的进度显示自定义控件渲染低于单选标签

呃-这种排列是怎么回事?

尽管定义了width和,height0按照跨度的默认行为,它仍然被计算为具有维度的元素。

对此的快速修复是添加display: flex.radio__input包装本机输入和自定义控件的跨度:

.radio__input {
  display: flex;
}
Enter fullscreen mode Exit fullscreen mode

Flex 尊重0尺寸,自定义控件弹出并充当其中的唯一元素.radio__input

添加 display: flex 来修复对齐的结果

步骤 3:改进输入与标签对齐

如果您使用过网格或弹性框,那么您现在的本能可能是应用align-items: center视觉调整输入相对于标签文本的对齐方式。

但是,如果标签太长,可能会断成多行怎么办?在这种情况下,沿水平中心对齐可能不太理想。

相反,让我们进行调整,以便输入相对于标签文本的第一行保持水平居中。

我们的第一步是调整line-height课程的跨度.radio__label

.radio__label {
  line-height: 1;
}
Enter fullscreen mode Exit fullscreen mode

毫无疑问,使用的值1在这里是一种快速修复方法,如果您的应用程序经常有多行无线电标签,那么这可能并不可取。

根据所使用的字体,这可能无法 100% 解决对齐问题,在这种情况下,您可能会受益于以下额外的调整。

在我们的自定义控件上,我们将使用它transform来将元素向上微调。这是一个有点神奇的数字,但作为起点,这个值是应用边框大小的一半。

.radio__control {
  // ...existing styles

  transform: translateY(-0.05em);
}
Enter fullscreen mode Exit fullscreen mode

这样,我们的对齐就完成了,并且对于单行和多行标签都可以使用:

输入文本与标签文本的最终对齐

第四步::checked国家

我们使用opacity: 0原生的单选输入来保持键盘交互以及点击/轻触交互的可访问性。

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

还记得我提到过顺序很重要吗?由于我们的自定义控件位于原生输入框之后,因此当原生控件处于🙌状态时,我们可以使用相邻的兄弟组合 - +- 来设置自定义控件的样式。:checked

选项 1:使用radial-gradient

我们可以添加radial-gradient一个经典的实心圆圈外观:

.radio__input {
  // ...existing styles

  input {
    // ...existing styles

    &:checked + .radio__control {
      background: radial-gradient(currentcolor 50%, rgba(255, 0, 0, 0) 51%);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

您可以根据自己的喜好调整渐变的停止点。

请注意,使用 来rgba定义透明颜色而不是关键字transparent,因为transparent在 Safari 中使用渐变时会将其解释为“透明黑色”👎

以下是结果的 gif:

使用径向渐变的自定义无线电选中状态的演示

由于radial-gradient应用为background,因此如果使用删除 CSS 背景的默认打印机设置打印表单页面,则它将不可见。

选项 2:使用:before

另一种方法是在自定义控件上使用:before呈现为圆形的子元素。

这种方法的优点是它也可以用于动画。

我们首先需要改变包装跨度的行为.radio__control

.radio__control {
  display:grid;
  place-items: center;
}
Enter fullscreen mode Exit fullscreen mode

:before这是将自定义控件的水平和垂直中心对齐的最快方法。

然后,我们创建:before元素,包括过渡并使用变换将其隐藏scale(0)

input + .radio__control::before {
  content: "";
  width: .5em;
  height: .5em;
  box-shadow: inset .5em .5em currentColor;
  border-radius: 50%;
  transition: 180ms transform ease-in-out;
  transform: scale(0);    
}
Enter fullscreen mode Exit fullscreen mode

使用 ofbox-shadow而不是 ofbackground-color将使无线电状态在打印时可见(h/t Alvaro Montoro)。

最后,当 为 时input:checked我们通过 使其可见,并scale(1)借助 获得漂亮的动画效果transition

input:checked + .radio__control::before {
  transform: scale(1);
}
Enter fullscreen mode Exit fullscreen mode

下面是使用动画元素的结果的 gif :before

使用 :before 自定义单选按钮选中状态的演示

第五步::focus国家

对于:focus状态,我们将使用双精度box-shadow来利用currentColor但确保基本自定义单选按钮和:focus样式之间的区别。

再次,我们将使用相邻兄弟组合器:

.radio__input {
  // ...existing styles

  input {
    // ...existing styles

    &:focus + .radio__control {
      box-shadow: 0 0 0 0.05em #fff, 0 0 0.15em 0.1em currentColor;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

定义的顺序box-shadow与其分层相对应,第一个定义等于“顶层”。这意味着在这个规则中,我们首先创建一个细白边框的外观,它出现在一个羽化阴影上方,该阴影的值取自currentColor

下面是一个 gif 来演示:focus外观:

自定义单选按钮聚焦状态的演示

这样,自定义单选按钮的基本样式就完成了!🎉

实验:使用:focus-within样式化标签文本

由于标签不是本机输入的兄弟,因此我们不能使用:focus输入的状态来设置其样式。

即将推出的伪选择器是:focus-within,其特点之一是它可以将样式应用于包含已获得焦点的元素的元素。

ModernCSS 一集还介绍了纯 CSS 可访问下拉导航菜单:focus-within

目前,:focus-within需要一个polyfill,因此以下样式应被视为增强功能,而不应被视为提供焦点视觉指示的唯一方法。

我们要做的第一个调整是添加transition并减少opacityradio__label

.radio__label {
  // ...existing styles
  transition: 180ms all ease-in-out;
  opacity: 0.8;
}
Enter fullscreen mode Exit fullscreen mode

确保降低的不透明度仍然满足调色板的适当对比度。

:focus-within然后,我们将通过在标签上添加规则( .radio) 来测试焦点。这意味着,当原生输入框(它是子元素,因此位于标签“内部”)获得焦点时,我们可以在焦点处于活动状态时为标签内的任何元素设置样式。

因此,我们将使用 稍微增加标签文本的视觉大小scale(),并重新提高不透明度。

.radio {
  // ...existing styles

    &:focus-within {
      .radio__label {
        transform: scale(1.05);
        opacity: 1;
      }
    }
}
Enter fullscreen mode Exit fullscreen mode

使用scale()可以防止调整大小影响元素的流动并导致抖动。过渡效果使其更加流畅,如下图所示:

自定义单选焦点状态的演示

演示

以下是整个解决方案,第一个无线电演示:checked使用的状态radial-gradient,第二个无线电演示使用的状态:before

查看自定义复选框样式,了解如何将样式扩展到:disabled状态,并了解如何使用 SVG 作为:checked指示器。

文章来源:https://dev.to/5t3ph/pure-css-custom-styled-radio-buttons-3dm5
PREV
SmolCSS.dev - 极简现代 CSS 代码片段
NEXT
纯 CSS 自定义复选框样式