使

使用纯 CSS 自定义选择样式

2025-06-04

使用纯 CSS 自定义选择样式

这是系列文章的第 20 集,该系列文章探讨了现代 CSS 解决方案,以解决我过去 13 多年来作为前端开发者所遇到的各种问题。访问ModernCSS.dev即可查看整个系列文章及其他资源

现代 CSS 为我们提供了一系列属性,以实现自定义选择样式,这些样式select对于顶级浏览器中的单个、多个和禁用元素具有几乎相同的初始外观。

我们的解决方案将使用一些属性和技术:

  • clip-path创建自定义下拉箭头
  • CSS 网格布局对齐原生选择和箭头
  • 自定义 CSS 变量以实现灵活的样式
  • em相对尺寸单位

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

本机选择的常见问题

与所有表单字段类型一样,<select>其初始外观在不同浏览器中有所不同。

从左到右,这是<select>Firefox、Chrome 和 Safari 中的初始外观:

初始原生选择外观,无自定义样式

差异包括框大小、字体大小、行高,最突出的是下拉指示器样式的差异。

我们的目标是在这些浏览器中创建相同的初始外观,包括多项选择和禁用状态。

注意:下拉列表仍然不支持样式设置,因此一旦<select>打开,它仍然会根据浏览器的不同,自动获取option列表的样式。这没关系——我们可以处理这个问题,以保留原生选择框的自由可访问性!

基本 HTML

<select>首先我们将集中讨论一个单曲。

<label for="standard-select">Standard Select</label>
<div class="select">
  <select id="standard-select">
    <option value="Option 1">Option 1</option>
    <option value="Option 2">Option 2</option>
    <option value="Option 3">Option 3</option>
    <option value="Option 4">Option 4</option>
    <option value="Option 5">Option 5</option>
    <option value="Option length">Option that has too long of a value to fit</option>
  </select>
</div>
Enter fullscreen mode Exit fullscreen mode

标签不是我们样式练习的一部分,但它作为一般要求包含在内,特别是for具有 值的属性id<select>

select为了实现我们的自定义样式,在本教程中,为了简单起见,我们将原生选择包装在一个带有类的额外 div 中。

重置和删除继承的样式

正如我的所有教程中所包含的现代最佳实践一样,我们首先添加以下重置:

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

接下来,我们可以开始制定本机规则select并应用以下内容来休息其外观:

select {
  // A reset of styles, including removing the default dropdown arrow
  appearance: none;
  // Additional resets for further consistency
  background-color: transparent;
  border: none;
  padding: 0 1em 0 0;
  margin: 0;
  width: 100%;
  font-family: inherit;
  font-size: inherit;
  cursor: inherit;
  line-height: inherit;
}
Enter fullscreen mode Exit fullscreen mode

虽然其中大多数可能都很熟悉,但奇怪的是appearance。这是一个不常用的属性,你会注意到它并没有达到我们想要的支持程度,但在本例中,它主要为我们提供了删除原生浏览器下拉箭头的功能。

注意:CodePen 已设置为使用自动前缀器,它将添加属性所需的前缀版本appearance。您可能需要为您的项目专门设置此功能,或手动添加。我的HTML / Sass Jumpstart已将自动前缀器作为生产版本的一部分。

好消息是,如果您需要的话,我们可以添加一条规则来删除较低 IE 版本中的箭头:

select::-ms-expand {
  display: none;
}
Enter fullscreen mode Exit fullscreen mode

这个技巧是在 Filament Group 的优秀文章中找到的,其中展示了创建选择样式的另一种方法

最后一步是删除默认的outline。别担心,我们稍后会添加一个替代的:focusstate!

select {
  // ...existing styles
  outline: none;
Enter fullscreen mode Exit fullscreen mode

下面是我们进度的动图。你可以看到,现在没有任何视觉迹象表明这是在select点击之前发生的:

与重置选择交互的演示

自定义选择框样式

首先,我们来设置一些 CSS 变量。这样我们的选择项就可以灵活地重新着色,例如表示错误状态。

:root {
  --select-border: #777;
  --select-focus: blue;
  --select-arrow: var(--select-border);
}
Enter fullscreen mode Exit fullscreen mode

可访问性注意事项:作为用户界面元素,选择边框与周围表面颜色的对比度必须达到 3:1 或更高。

现在是时候创建我们将应用于包装的自定义选择样式了div.select

.select {
  width: 100%;
  min-width: 15ch;
  max-width: 30ch;
  border: 1px solid var(--select-border);
  border-radius: 0.25em;
  padding: 0.25em 0.5em;
  font-size: 1.25rem;
  cursor: pointer;
  line-height: 1.1;
  background-color: #fff;
  background-image: linear-gradient(to top, #f9f9f9, #fff 33%);
}
Enter fullscreen mode Exit fullscreen mode

首先,我们设置一些宽度约束。min-width和 的max-width值主要用于本演示,您可以根据自己的使用情况选择删除或修改它们。

然后我们应用一些盒子模型属性,包括borderborder-radiuspadding。注意单位的使用em,它将使这些属性与集合保持比例font-size

在重置样式中,我们为 设置了几个属性inherit,因此我们在这里定义这些属性,包括font-sizecursorline-height

最后,我们为其提供了背景属性,包括用于稍微增加维度的渐变。如果删除背景属性,选中内容将变为透明,并会拾取页面背景。这可能是理想的选择,但请注意并测试对比度效果。

以下是我们的进展:
更新的选择现在具有明显的框外观

自定义选择下拉箭头

对于我们的下拉箭头,我们将使用最令人兴奋的现代 CSS 属性之一:clip-path

剪切路径让我们能够通过“剪切”大多数元素默认的正方形和矩形来创建各种形状。我clip-path最近的作品集网站重新设计中很享受使用这个功能。

在获得更好的支持之前clip-path,替代方法包括:

  • background-image- 通常是 png,稍微现代一点的是 SVG
  • 内联 SVG 作为附加元素
  • 创建三角形的边框技巧

SVG 或许感觉像是最佳解决方案,然而当它被用作图标时,background-image它就失去了图标的功能,因为除非完全重新定义,否则无法更改其属性(例如填充颜色)。这意味着我们无法使用 CSS 自定义变量。

将 SVG 放置在内联位置可以解决fill颜色问题,但这意味着每次<select>定义时都要包含一个以上的元素。

通过clip-path,我们得到了一个清晰、可扩展的箭头“图形”,感觉就像一个 SVG,但具有能够使用我们的自定义变量并包含在样式与 HTML 标记中的好处。

为了创建箭头,我们将其定义为::after伪元素。

.select::after {
  content: "";
  width: 0.8em;
  height: 0.5em;
  background-color: var(--select-arrow);
  clip-path: polygon(100% 0%, 0 0%, 50% 100%);
}
Enter fullscreen mode Exit fullscreen mode

语法clip-path有点奇怪,而且由于这不是本文的重点,因此我推荐以下资源:

width如果你一直在关注,你可能会注意到,尽管定义了和 ,但箭头并没有出现height。检查后发现,::after的宽度实际上并不允许。

我们将通过更新.select以使用 CSS 网格布局来解决此问题。

.select {
  // ...existing styles
  display: grid;
}
Enter fullscreen mode Exit fullscreen mode

这样,通过本质上扩展类似于“块”的显示值,箭头就会出现。

剪辑路径箭头现在出现在本机选择下方

在此阶段,我们可以验证我们确实创建了一个三角形。

为了修复对齐问题,我们将使用我最喜欢的 CSS 网格 hack(如果你读过这里的几篇文章,这对你来说已经很熟悉了!)。

旧 CSS 解决方案:position: absolute
新 CSS 解决方案:单一解决方案grid-template-areas包含所有内容

首先我们定义一个区域,然后定义select::after都使用它。该区域的名称限定于其创建时所针对的元素,为了方便起见,我们将其命名为“select”:

.select {
  // ...existing styles
  grid-template-areas: "select";
}

select,
.select:after {
  grid-area: select;
}
Enter fullscreen mode Exit fullscreen mode

由于通过源顺序的堆叠上下文,这给我们带来了本机选择上方箭头的重叠:

原生选择上方更新箭头位置的预览

我们现在可以使用网格属性来确定每个元素的对齐方式:

.select {
  // ...existing styles
  align-items: center;
}

.select:after {
  // ...existing styles
  justify-self: end;
}
Enter fullscreen mode Exit fullscreen mode

哒哒!

完成自定义选择的初始样式

:focus状态

哦对了,还记得我们是怎么删除的吗outline?我们需要解决:focus删除后丢失的状态问题。

我们可以使用一个即将推出的属性,:focus-within但此时最好为其包含一个 polyfill。

在本教程中,我们将使用另一种方法来达到相同的结果,只是稍微繁琐一些。

不幸的是,这意味着我们需要在 DOM 中添加一个元素。

在原生选择元素之后,作为 中的最后一个子元素.select,添加:

<span class="focus"></span>
Enter fullscreen mode Exit fullscreen mode

为什么是“之后”?因为这是一个纯 CSS 解决方案,将它放在原生选择器之后意味着我们可以在select获得焦点时,通过使用相邻的兄弟选择器来修改它+

这使我们能够创建以下规则:

select:focus + .focus {
  position: absolute;
  top: -1px;
  left: -1px;
  right: -1px;
  bottom: -1px;
  border: 2px solid var(--select-focus);
  border-radius: inherit;
}
Enter fullscreen mode Exit fullscreen mode

您可能想知道为什么我们position: absolute在刚刚学习了之前的grid-area技巧之后又回到了这里。

这样做是为了避免根据内边距重新计算调整值。如果您自己尝试一下,就会发现即使将 设置widthheight100%,它仍然位于内边距内。

这项工作position: absolute最擅长的是匹配元素的大小。我们在每个方向上额外拉一个像素,以确保它与 border 属性重叠。

但是,我们需要再添加一个以.select确保它与我们的选择相关 - 嗯position: relative

.select {
  // ...existing styles
  position: relative;
Enter fullscreen mode Exit fullscreen mode

以下是我们在 Chrome 中看到的自定义全选:

在自定义选择中聚焦并选择选项的 gif 演示

多选

选择还有第二种形式,允许用户选择多个选项。从 HTML 的角度来看,这仅仅意味着添加一个multiple属性,但我们还会添加一个类来帮助创建样式调整,它被称为select--multiple

<label for="multi-select">Multiple Select</label>
<div class="select select--multiple">
  <select id="multi-select" multiple>
    <option value="Option 1">Option 1</option>
    <option value="Option 2">Option 2</option>
    <option value="Option 3">Option 3</option>
    <option value="Option 4">Option 4</option>
    <option value="Option 5">Option 5</option>
    <option value="Option length">Option that has too long of a value to fit</option>
  </select>
  <span class="focus"></span>
</div>
Enter fullscreen mode Exit fullscreen mode

看看它,我们可以看到它继承了我们的大多数风格,只是我们不需要这个视图中的箭头。

使用先前定义的继承样式进行多项选择

这是一个快速修复方法,用于调整定义箭头的选择器。我们用它:not()来排除新定义的类:

.select:not(.select--multiple)::after
Enter fullscreen mode Exit fullscreen mode

我们对多项选择做了一些小的调整,首先是删除之前添加的填充以便为箭头腾出空间:

select[multiple] {
  padding-right: 0;
}
Enter fullscreen mode Exit fullscreen mode

默认情况下,具有长值的选项将溢出可见区域并被剪裁,但我发现主要浏览器允许根据需要覆盖包装:

select[multiple] option {
  white-space: normal;
}
Enter fullscreen mode Exit fullscreen mode

可选地,我们可以在选择框上设置一个,height以实现更可靠的跨浏览器行为。通过测试,我发现 Chrome 和 Firefox 会显示部分选项,但 Safari 会完全隐藏无法完全显示的选项。

高度必须直接在原生选择器上设置。考虑到我们的其他样式,该值6rem将能够显示 3 个选项:

select[multiple] {
  // ...existing styles
  height: 6rem;
}
Enter fullscreen mode Exit fullscreen mode

目前,由于当前的浏览器支持,我们已尽可能地做出调整。

:selectedChrome 中的状态options自定义程度较高,Firefox 中则略有不同,Safari 中则完全无法自定义。CodePen演示中有一个部分可以取消注释来预览。

:disabled样式

虽然我主张简单地不显示禁用的控件,但我们应该为该状态准备样式以覆盖我们的基础。

为了强调禁用状态,我们想应用灰色背景。但是由于我们已经设置了背景样式,.select并且没有:parent选择器,所以我们需要创建最后一个类来处理这个状态:

.select--disabled {
  cursor: not-allowed;
  background-color: #eee;
  background-image: linear-gradient(to top, #ddd, #eee 33%);
}
Enter fullscreen mode Exit fullscreen mode

在这里,我们更新了光标,作为该字段无法交互的额外提示,并将我们之前设置为白色的背景值更新为禁用状态的灰色。

这会导致以下结果:

先前的单选和多选禁用状态样式

演示

您可以自己测试一下,但这里是 Firefox、Chrome 和 Safari(从左到右)完整解决方案的预览:

最终样式选择器在提到的浏览器中

文章来源:https://dev.to/5t3ph/custom-select-styles-with-pure-css-4f58
PREV
扩展“box-shadow”和“border-radius”的使用
NEXT
仅使用 CSS 实现全宽响应式图像的两种方法