Selektor multimediów (wybór z możliwością dostosowania)

Element natywny <select> był w przeszłości trudny do stylizowania i mógł zawierać tylko opcje zwykłego tekstu. Właściwość appearance: base-select umożliwia deklaratywne, oparte wyłącznie na CSS, przejście do stanu dostosowywania elementu <select>. Dzięki temu deweloperzy mogą umieszczać w elementach <option> rozbudowane treści HTML, takie jak obrazy, pliki SVG i złożone układy, zachowując przy tym natywną dostępność klawiatury i integrację z formularzami. Użyj tego wzorca, aby zastąpić ciężkie, niestandardowe komponenty wyboru standardowymi, natywnymi elementami.

Jak to zrobić?

Aby zaimplementować selektor multimediów za pomocą interfejsu Customizable Select API:

  1. Włączanie stylów podstawowych: zastosuj appearance: base-select zarówno do elementu <select>, jak i jego wewnętrznego selektora za pomocą pseudoelementu ::picker(select). Spowoduje to zmianę parsera HTML przeglądarki w przypadku zawartości wewnątrz elementu <select>.
  2. Zdefiniuj zawartość przycisku: użyj standardowych elementów <button> i <selectedcontent> wewnątrz elementu <select>, aby określić, co ma być wyświetlane, gdy selektor jest zamknięty. Element <selectedcontent> automatycznie odzwierciedla zawartość wybranej opcji. Jest to wymagane, jeśli chcesz wyświetlać obecnie wybrane szczegółowe treści na przycisku.
  3. Używanie szczegółowych treści w opcjach: możesz teraz umieszczać obrazy, pliki SVG i inne tagi HTML w tagach <option>. Przed wprowadzeniem tego interfejsu API przeglądarka usuwała tagi z tagów <option> i renderowała tylko zwykły tekst.
  4. Stylizacja wyskakującego okienka: stylizuj listę opcji, kierując na pseudoelement ::picker(select). Jest renderowany w górnej warstwie, co oznacza, że nie musisz walczyć z z-index. Opcje są pozycjonowane za pomocą interfejsu Anchor Positioning API.

Przykładowy kod: rozbudowany selektor ról

<label for="role-picker">Select your role</label>
<select class="custom-select" name="role" id="role-picker">
  <button>
    <selectedcontent></selectedcontent> <!-- Mirrors the selected option's content automatically so you do not need JS to update the button -->
  </button>

  <!-- Define concise aria-label values on options whose mirrored rich content would otherwise read awkwardly as a concatenated string -->
  <option value="frontend" aria-label="Frontend Developer">
    <svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
      <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
    </svg>
    <div class="option-text">
      <span class="option-title">Frontend Developer</span>
      <span class="option-desc">React, Vue, CSS</span>
    </div>
  </option>

  <option value="backend" aria-label="Backend Developer">
    <svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
      <rect x="2" y="12" width="20" height="14" rx="2" ry="2"></rect>
    </svg>
    <div class="option-text">
      <span class="option-title">Backend Developer</span>
      <span class="option-desc">Node.js, Python</span>
    </div>
  </option>
</select>
select.custom-select,
select.custom-select::picker(select) {
  appearance: base-select; /* MUST opt-in both the select and its picker to enable the customizable state, otherwise browser standard rendering applies */
}

select.custom-select {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 12px;
  background-color: #1e293b;
  border: 1px solid #334155;
  border-radius: 8px;
  color: #f1f5f9;
  cursor: pointer;
}

select.custom-select::picker(select) {
  background-color: #0f172a;
  border: 1px solid #334155;
  border-radius: 8px;
  padding: 8px;
  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
  width: anchor-size(width); /* Uses Anchor Positioning API to keep the dropdown precisely aligned to the button trigger width */
}

select.custom-select option {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px;
  border-radius: 4px;
  cursor: pointer;
}

select.custom-select option:hover {
  background-color: #1e293b;
}

/* Remove standard OS checkmark for base-select */
select.custom-select option::before {
  display: none;
}

/* MANDATORY: Provide multiple visual indicators (e.g., prominent background color and bold title font) to communicate the checked state cleanly */
select.custom-select option:checked {
  background-color: #3b82f6;
  color: #ffffff;
}
select.custom-select option:checked .option-title {
  font-weight: 700;
}

Strategiczne wdrażanie i sprawdzone metody

  • UŻYWAJ elementu appearance: base-select, gdy potrzebujesz złożonych układów opcji (ikon, opisów, obrazów), które wcześniej były możliwe tylko przy użyciu ciężkich platform interfejsu JavaScript.
  • NIE używaj elementów ad hoc, jeśli zauważysz opóźnienia w działaniu. Przeglądarka obsługuje natywnie zaznaczanie klawiaturą.
  • DO account for top-layer rendering. Selektor jest renderowany w górnej warstwie, co oznacza, że zastępuje względne z-index zawartości strony.
  • UKRYWAJ dodatkowe szczegóły (np. opisy) w stanie przycisku, jeśli zajmują zbyt dużo miejsca, przez stylizowanie .custom-select selectedcontent .option-desc { display: none; }.
  • Przetestuj działanie układu. Ustawienie appearance: base-select usuwa domyślne działanie przeglądarki polegające na określaniu rozmiaru elementu select na podstawie szerokości najdłuższej opcji. Aby zapobiec przesunięciom układu, może być konieczne ustawienie stałej szerokości lub użycie ograniczeń flex/grid.
  • Upewnij się, że element <select> ma atrybut name i powiązany element <label>. Dzięki temu nawet w przypadku niestandardowego interfejsu komponent pozostaje dostępny dla czytników ekranu i działa prawidłowo ze standardowymi formularzami.
  • Użyj standardowych atrybutów value w opcjach, aby przesyłanie formularzy działało dokładnie tak jak wcześniej.

Strategie zastępcze

Kreacje zastępcze i obsługa przez przeglądarki w przypadku formatu <select> z możliwością dostosowania

W przypadku przeglądarek, które nie obsługują jeszcze appearance: base-select, element <select> jest zastępowany standardowym menu rozwijanym systemu operacyjnego.

  • Ignorowanie treści nietekstowych: starsze przeglądarki usuwają tagi HTML (takie jak <svg> lub <div>) wewnątrz tagów <option> i renderują tylko węzły tekstowe. Upewnij się, że treść tekstowa elementu <option> jest czytelna i ma sens sama w sobie.
  • Obsługa struktury HTML: standardowe parsery mogą ignorować tagi <button> i <selectedcontent> wewnątrz tagu <select> lub traktować je jako nieprawidłowe. Jeśli standardowy tekst jest dla Ciebie czytelną opcją zastępczą, do progresywnego ulepszania nie są wymagane żadne obciążające skrypty polyfill JavaScript.
document.addEventListener("DOMContentLoaded", () => {
  // Check if browser supports base-select value
  if (!CSS.supports("appearance", "base-select")) {
    // Custom select overrides are not supported natively.
  }
});