Het native <select>-element was vroeger moeilijk op te maken en kon alleen opties voor platte tekst bevatten. De property appearance: base-select biedt een declaratieve, CSS-only manier om een aanpasbare status voor het element <select> te kiezen. Zo kunnen ontwikkelaars uitgebreide HTML-content, zoals afbeeldingen, SVG's en complexe indelingen, opnemen in <option>-elementen, terwijl de toegankelijkheid van het systeemeigen toetsenbord en de formulierintegratie behouden blijven. Gebruik dit patroon om zware, op maat gemaakte selectiecomponenten te vervangen door standaard, native elementen.
Implementeren
Zo implementeer je een mediakiezer voor rich media met de Customizable Select API:
- Aanmelden voor basisstijlen: Pas
appearance: base-selecttoe op zowel het element<select>als de interne kiezer met het pseudoelement::picker(select). Hierdoor wordt de HTML-parser van de browser gewijzigd voor de content in de<select>. - Definieer de knopcontent: Gebruik standaard
<button>- en<selectedcontent>-elementen in de<select>om te definiëren wat wordt getoond als de kiezer is gesloten. Het element<selectedcontent>spiegelt automatisch de content van de geselecteerde optie. Dit is vereist als je de geselecteerde uitgebreide content in de knop wilt tonen. - Uitgebreide content gebruiken in opties: Je kunt nu afbeeldingen, SVG's en andere HTML-tags in
<option>-tags plaatsen. Vóór deze API verwijderde de browser tags uit<option>-tags en werd alleen platte tekst gerenderd. - De popover opmaken: Maak de lijst met dropdownopties op door het pseudo-element
::picker(select)te targeten. Het wordt weergegeven in de bovenste laag, wat betekent dat je niet hoeft te vechten metz-index. Opties worden gepositioneerd met de Anchor Positioning API.
Voorbeeldcode: Uitgebreide rolkiezer
<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;
}
Strategische implementatie en best practices
- Gebruik WEL
appearance: base-selectals je complexe optie-indelingen nodig hebt (iconen, beschrijvingen, afbeeldingen) die eerder alleen mogelijk waren met zware JavaScript UI-frameworks. - Gebruik GEEN ad-hoc-elementen als je prestatievertragingen opmerkt. De browser verwerkt de native toetsenbordfocus native.
- Houd rekening met rendering van de bovenste laag. De kiezer wordt weergegeven in de bovenste laag, wat betekent dat deze de relatieve
z-indexvan de paginacontent overschrijft. - WEL: Verberg secundaire details (zoals beschrijvingen) in de knopstatus als ze te veel ruimte innemen door
.custom-select selectedcontent .option-desc { display: none; }te stylen. - Test het gedrag van de lay-out. Als je
appearance: base-selectinstelt, wordt het standaard browsergedrag verwijderd om de selectie te schalen op basis van de breedte van de langste optie. U moet misschien een vaste breedte instellen of flex-/rasterbeperkingen gebruiken om indelingsverschuivingen te voorkomen. - Zorg dat uw
<select>een kenmerknameen een bijbehorende<label>heeft. Zo blijft de component zelfs met een aangepaste UI toegankelijk voor schermlezers en werkt deze correct met standaard formulierinzendingen. - Gebruik standaard
value-kenmerken voor opties om te zorgen dat het indienen van formulieren precies zo werkt als voorheen.
Reservestrategieën
Fallbacks en browserondersteuning voor aanpasbare <select>
Voor browsers die appearance: base-select nog niet ondersteunen, wordt het element <select> op een nette manier gedegradeerd naar een standaard dropdownmenu van het besturingssysteem.
- Niet-tekstcontent genegeerd: Oudere browsers verwijderen HTML-tags (zoals
<svg>of<div>) binnen<option>-tags en geven alleen de tekstnodes weer. Zorg dat de tekstcontent van de<option>op zichzelf leesbaar en betekenisvol is. - Verwerking van HTML-structuur: Standaard parsers negeren de tags
<button>en<selectedcontent>in<select>of behandelen ze als ongeldig. Er zijn geen zware JavaScript-polyfills vereist voor progressive enhancement als je standaardtekst als een leesbare fallback beschouwt.
document.addEventListener("DOMContentLoaded", () => {
// Check if browser supports base-select value
if (!CSS.supports("appearance", "base-select")) {
// Custom select overrides are not supported natively.
}
});