可定製的 select 元素

本文解釋瞭如何使用實驗性瀏覽器功能建立完全自定義的<select>元素。這包括完全控制select按鈕、下拉選擇器、箭頭圖示、當前選擇的複選標記以及每個單獨的<option>元素的樣式。

警告:本文中演示的CSS和HTML功能目前瀏覽器支援有限;請檢視各個功能參考頁面上的瀏覽器相容性表格以獲取更多詳細資訊。一些JavaScript框架會阻止這些功能;在其他框架中,它們在啟用伺服器端渲染(SSR)時會導致水合失敗。

Background

傳統上,自定義<select>元素的外觀和感覺一直很困難,因為它們包含在作業系統級別設定樣式的內部元件,這些元件無法使用CSS進行定位。這包括下拉選擇器、箭頭圖示等。

以前,除了使用自定義JavaScript庫之外,最好的選擇是在<select>元素上設定appearance值為none,以去除一些OS級別的樣式,然後使用CSS自定義可設定樣式的部分。這種技術在高階表單樣式中有所解釋。

可定製的<select>元素為這些問題提供瞭解決方案。它們允許您僅使用HTML和CSS構建如下所示的示例,這些示例在支援的瀏覽器中完全自定義。這包括<select>和下拉選擇器佈局、配色方案、圖示、字型、過渡、定位、指示選定圖示的標記等。

此外,它們在現有功能的基礎上提供了漸進增強,在不支援的瀏覽器中退回到“經典”select。

您將在下面的部分中找到如何構建此示例。

哪些功能構成了可定製的select?

您可以使用以下HTML和CSS功能構建可定製的<select>元素

  • 普通的<select><option><optgroup>元素。它們與“經典”select的功能相同,只是它們允許的內容型別更多。
  • 一個<button>元素作為<select>元素的第一個子元素包含在內,這在“經典”select中以前是不允許的。當它包含在內時,它會替換關閉的<select>元素的預設“按鈕”渲染。這通常被稱為select按鈕(因為它是您需要按下才能開啟下拉選擇器的按鈕)。

    注意:select按鈕預設是惰性的,因此如果其中包含互動式子元素(例如連結或按鈕),它仍將被視為用於互動的單個按鈕——例如,子項將不可聚焦或不可點選。

  • <selectedcontent>元素可以可選地包含在<select>元素的第一個子<button>元素中,以在關閉<select>元素中顯示當前選定的值。它包含當前選定的<option>元素內容的克隆(在底層使用cloneNode()建立)。
  • ::picker(select)偽元素,它定位選擇器的全部內容。這包括<select>元素內除第一個子<button>之外的所有元素。
  • appearance屬性值base-select,它使<select>元素和::picker(select)偽元素選擇瀏覽器定義的自定義select的預設樣式和行為。
  • :open偽類,它在選擇器(::picker(select))開啟時定位select按鈕。
  • ::picker-icon偽元素,它定位select按鈕內的圖示——當select關閉時指向下方的箭頭。
  • :checked偽類,它定位當前選定的<option>元素。
  • ::checkmark偽元素,它定位放置在當前選定的<option>元素內的複選標記,以提供視覺指示哪個被選中。

此外,<select>元素及其下拉選擇器會自動分配以下行為

  • 它們具有呼叫者/彈出視窗關係,如彈出視窗API所指定,它提供了透過:popover-open偽類選擇開啟時選擇器的能力。有關彈出視窗行為的更多詳細資訊,請參閱使用彈出視窗API
  • 它們具有隱式錨點引用,這意味著選擇器透過CSS錨點定位自動與<select>元素關聯。瀏覽器預設樣式將選擇器定位在相對於按鈕(錨點)的位置,您可以按照定位相對於其錨點的元素中解釋的方式自定義此位置。瀏覽器預設樣式還定義了一些位置嘗試回退,如果選擇器有溢位視口的危險,則會重新定位它。位置嘗試回退在處理溢位:嘗試回退和條件隱藏中有所解釋。

注意:您可以透過檢視相關功能(如<selectedcontent>::picker(select)::checkmark)的參考頁面上的瀏覽器相容性表格來檢查可定製<select>的瀏覽器支援情況。

讓我們透過頁面頂部顯示的示例來檢視所有上述功能的實際應用。

可定製的select標記

我們的示例是一個典型的<select>選單,允許您選擇一隻寵物。標記如下

html
<form>
  <p>
    <label for="pet-select">Select pet:</label>
    <select id="pet-select">
      <button>
        <selectedcontent></selectedcontent>
      </button>

      <option value="">Please select a pet</option>
      <option value="cat">
        <span class="icon" aria-hidden="true">🐱</span
        ><span class="option-label">Cat</span>
      </option>
      <option value="dog">
        <span class="icon" aria-hidden="true">🐶</span
        ><span class="option-label">Dog</span>
      </option>
      <option value="hamster">
        <span class="icon" aria-hidden="true">🐹</span
        ><span class="option-label">Hamster</span>
      </option>
      <option value="chicken">
        <span class="icon" aria-hidden="true">🐔</span
        ><span class="option-label">Chicken</span>
      </option>
      <option value="fish">
        <span class="icon" aria-hidden="true">🐟</span
        ><span class="option-label">Fish</span>
      </option>
      <option value="snake">
        <span class="icon" aria-hidden="true">🐍</span
        ><span class="option-label">Snake</span>
      </option>
    </select>
  </p>
</form>

注意:圖示上包含aria-hidden="true"屬性,以便它們對輔助技術隱藏,避免選項值被宣佈兩次(例如,“貓 貓”)。

示例標記與“經典”<select>標記幾乎相同,但有以下區別

  • <button><selectedcontent></selectedcontent></button>結構表示select <button>。新增<selectedcontent>元素會導致瀏覽器將當前選定的<option>克隆到按鈕內,然後您可以為其提供自定義樣式。如果此結構未包含在您的標記中,瀏覽器將退回到在預設按鈕內渲染選定選項的文字,您將無法輕鬆對其進行樣式設定。

    注意:可以<button>中包含任意內容,以在關閉的<select>內渲染您想要的任何內容,但請注意這樣做。您包含的內容可能會改變暴露給輔助技術的<select>元素的輔助值。

  • <select>的其餘內容表示下拉選擇器,通常僅限於表示選擇器中不同選擇的<option>元素。您可以在選擇器中包含其他內容,但不建議這樣做。

  • 傳統上,<option>元素只能包含文字,但在可定製的select中,您可以包含其他標記結構,如影像、其他非互動式文字級語義元素等。您甚至可以使用::before::after偽元素來包含其他內容,但請記住,這不會包含在可提交的值中。在我們的示例中,每個<option>包含兩個<span>元素,分別包含一個圖示和一個文字標籤,允許它們各自獨立地進行樣式設定和定位。

    注意:由於<option>內容可以包含多級DOM子樹,而不僅僅是文字節點,因此關於瀏覽器應如何透過JavaScript提取當前<select>存在規則。將檢索選定的<option>元素的textContent屬性值,對其執行trim(),並將結果設定為<select>值。

這種設計允許不支援的瀏覽器回退到經典的<select>體驗。<button><selectedcontent></selectedcontent></button>結構將被完全忽略,非文字<option>內容將被剝離,只留下文字節點內容,但結果仍將起作用。

選擇自定義select渲染

要選擇自定義select功能和最小的瀏覽器基礎樣式(並刪除作業系統提供的樣式),您的<select>元素及其下拉選擇器(由::picker(select)偽元素表示)都需要設定appearance值為base-select

css
select,
::picker(select) {
  appearance: base-select;
}

您可以選擇只對<select>元素啟用新功能,而將選擇器保留預設的作業系統樣式,但在大多數情況下,您會希望同時啟用兩者。您不能在不啟用<select>元素的情況下啟用選擇器。

完成此操作後,結果是一個非常簡單的<select>元素渲染

您現在可以隨意對其進行樣式設定。首先,<select>元素設定了自定義的borderbackground(在:hover:focus時會改變)和padding值,以及一個transition,以便背景變化平滑動畫

css
select {
  border: 2px solid #dddddd;
  background: #eeeeee;
  padding: 10px;
  transition: 0.4s;
}

select:hover,
select:focus {
  background: #dddddd;
}

選擇器圖示的樣式

要為select按鈕內的圖示(當select關閉時指向下方的箭頭)設定樣式,您可以使用::picker-icon偽元素對其進行定位。以下程式碼為圖示提供自定義的color和一個transition,以便其rotate屬性的變化平滑動畫

css
select::picker-icon {
  color: #999999;
  transition: 0.4s rotate;
}

接下來,::picker-icon:open偽類結合使用——它只在下拉選擇器開啟時定位select按鈕——以便在<select>開啟時為圖示提供180degrotate值。

css
select:open::picker-icon {
  rotate: 180deg;
}

讓我們看看目前為止的工作——注意當<select>開啟和關閉時,選擇器箭頭如何平滑地旋轉180度

下拉選擇器的樣式

下拉選擇器可以使用::picker(select)偽元素進行定位。如前所述,選擇器包含<select>元素內除按鈕和<selectedcontent>之外的所有內容。在我們的示例中,這意味著所有<option>元素及其內容。

首先,選擇器的預設黑色border被移除

css
::picker(select) {
  border: none;
}

現在對<option>元素進行樣式設定。它們使用flexbox進行佈局,將它們全部對齊到flex容器的開頭,並在每個元素之間包含20pxgap。每個<option>還具有與<select>相同的borderbackgroundpaddingtransition,以提供一致的外觀和感覺

css
option {
  display: flex;
  justify-content: flex-start;
  gap: 20px;

  border: 2px solid #dddddd;
  background: #eeeeee;
  padding: 10px;
  transition: 0.4s;
}

注意:可定製的<select>元素<option>預設設定display: flex,但為了說明正在發生的事情,它仍然包含在我們的樣式表中。

接下來,使用:first-of-type:last-of-type:not()偽類的組合,在頂部和底部<option>元素上設定適當的border-radius,並從所有<option>元素(最後一個除外)中移除border-bottom,以避免邊框看起來混亂和雙重。我們還在外部::picker(select)容器上設定相同的border-radius,這樣如果我們在頁面上設定不同的背景顏色,就不會出現醜陋的方形白色框。

css
option:first-of-type {
  border-radius: 8px 8px 0 0;
}

option:last-of-type {
  border-radius: 0 0 8px 8px;
}

::picker(select) {
  border-radius: 8px;
}

option:not(option:last-of-type) {
  border-bottom: none;
}

接下來,使用:nth-of-type(odd)為奇數<option>元素設定不同的background顏色以實現斑馬條紋,並在焦點和懸停時為<option>元素設定不同的background顏色,以在選擇期間提供有用的視覺高亮

css
option:nth-of-type(odd) {
  background: white;
}

option:hover,
option:focus {
  background: plum;
}

最後,本節為<option>圖示(包含在帶有icon類的<span>元素中)設定了更大的font-size,使其更大,並使用text-box屬性刪除了圖標表情符號在塊開始和塊結束邊緣的一些惱人的間距,使其與文字標籤更好地對齊

css
option .icon {
  font-size: 1.6rem;
  text-box: trim-both cap alphabetic;
}

我們的示例現在渲染如下

調整select按鈕內選定選項內容的樣式

如果您從最近的幾個即時示例中選擇任何寵物選項,您會注意到一個問題——寵物圖示導致select按鈕的高度增加,這也改變了選擇器圖示的位置,並且選項圖示和標籤之間沒有間距。

這可以透過在包含在<selectedcontent>中時隱藏圖示來解決,<selectedcontent>表示選定<option>的內容在select按鈕內的顯示方式。在我們的示例中,它使用display: none隱藏

css
selectedcontent .icon {
  display: none;
}

這不會影響<option>內容在下拉選擇器中顯示時的樣式。

當前選定選項的樣式

要設定下拉選擇器中當前選定<option>的樣式,您可以使用:checked偽類進行定位。這用於將選定<option>元素的font-weight設定為bold

css
option:checked {
  font-weight: bold;
}

當前選擇複選標記的樣式

您可能已經注意到,當您開啟選擇器進行選擇時,當前選定的<option>在其行開始端有一個複選標記。此複選標記可以使用::checkmark偽元素進行定位。例如,您可能希望隱藏此複選標記(例如,透過display: none)。

您也可以選擇對其進行一些更有趣的操作——前面<option>元素是使用flexbox水平佈局的,flex專案對齊到行的開頭。在下面的規則中,透過設定一個大於0order值,並使用automargin-left值將其對齊到行的末尾,將複選標記從行的開頭移動到末尾(參見對齊和自動邊距)。

最後,將content屬性的值設定為不同的表情符號,以設定要顯示的不同圖示。

css
option::checkmark {
  order: 1;
  margin-left: auto;
  content: "☑️";
}

注意:::checkmark::picker-icon偽元素不包含在輔助功能樹中,因此在它們上設定的任何生成的content都不會被輔助技術宣佈。您仍然應該確保您設定的任何新圖示在視覺上都符合其預期目的。

讓我們再次檢視示例的渲染情況。最後三節更新後的狀態如下

使用彈出視窗狀態為選擇器新增動畫效果

可定製的<select>元素的select button和下拉選擇器會自動獲得呼叫者/彈出視窗關係,如使用彈出視窗API中所述。這為<select>元素帶來了許多優勢;我們的示例利用了使用過渡在彈出視窗隱藏和顯示狀態之間進行動畫的能力。:popover-open偽類表示處於顯示狀態的彈出視窗。

本節將快速介紹該技術——閱讀動畫彈出視窗以獲得更詳細的描述。

首先,選擇器使用::picker(select)被選中,並被賦予0opacity值和all 0.4s allow-discretetransition值。這會導致當彈出視窗狀態從隱藏變為顯示時,所有更改值的屬性都進行動畫。

css
::picker(select) {
  opacity: 0;
  transition: all 0.4s allow-discrete;
}

過渡屬性列表包括opacity,但它還包括兩個由瀏覽器預設樣式設定的離散屬性

display

當彈出視窗狀態從隱藏變為顯示時,display值從none變為block。這需要進行動畫以確保其他過渡可見。

overlay

當彈出視窗狀態從隱藏變為顯示時,overlay值從none變為auto,以將其提升到頂層,然後在隱藏時再次恢復,以將其移除。這需要進行動畫以確保在過渡完成之前延遲從頂層移除彈出視窗,從而確保過渡可見。

注意:需要allow-discrete值才能啟用離散屬性動畫。

接下來,在顯示狀態下使用::picker(select):popover-open選擇選擇器,並將其opacity值設定為1——這是過渡的結束狀態

css
::picker(select):popover-open {
  opacity: 1;
}

最後,由於選擇器在從display: none過渡到使其可見的display值時正在進行過渡,因此過渡的起始狀態必須在@starting-style塊內指定

css
@starting-style {
  ::picker(select):popover-open {
    opacity: 0;
  }
}

這些規則協同工作,使選擇器在<select>開啟和關閉時平滑地淡入淡出。

使用錨點定位選擇器

可定製的<select>元素的select按鈕和下拉選擇器具有隱式錨點引用,並且選擇器透過CSS錨點定位自動與select按鈕關聯。這意味著不需要使用anchor-nameposition-anchor屬性進行顯式關聯。

此外,瀏覽器的預設樣式提供了預設位置,您可以按照定位相對於其錨點的元素中解釋的方式自定義此位置。

在我們的演示中,選擇器的位置是相對於其錨點設定的,透過在其topleft屬性值中使用anchor()函式

css
::picker(select) {
  top: calc(anchor(bottom) + 1px);
  left: anchor(10%);
}

這導致選擇器的頂部邊緣始終定位在select按鈕底部邊緣下方1畫素處,選擇器的左邊緣始終定位在select按鈕左邊緣的10%寬度處。

注意:如果您想刪除隱式錨點引用,以阻止選擇器錨定到<select>元素,您可以透過將選擇器的position-anchor屬性設定為當前文件中不存在的錨點名稱,例如--not-an-anchor-name來實現。另請參閱刪除錨點關聯

最終結果

經過最後兩節,我們的<select>的最終更新狀態渲染如下

自定義其他經典select功能

以上各節涵蓋了可定製select中所有可用的新功能,並展示了它如何與經典單行select以及彈出視窗和錨點定位等相關現代功能進行互動。還有一些上面未提及的其他<select>元素功能;本節討論它們目前如何與可定製select協同工作

<select multiple>

目前還沒有為可定製<select>元素上的multiple屬性指定任何支援,但將來會對此進行研究。

<optgroup>

<optgroup>元素的預設樣式與經典<select>元素相同——加粗且縮排少於所包含的選項。您需要確保對<optgroup>元素進行樣式設定,使其符合整體設計,並請記住它們將像常規HTML中預期的容器一樣行事。在可定製的<select>元素中,<legend>元素被允許作為<optgroup>的子元素,以提供易於定位和設定樣式的標籤。這會替換<optgroup>元素的label屬性中設定的任何文字,並且它具有相同的語義。

接下來

在本模組的下一篇文章中,我們將探討現代瀏覽器中可用於為不同狀態的表單設定樣式的不同UI偽類

另見