De aanpasbare selectie-API biedt een declaratieve, CSS-gestuurde manier om <select>-elementen en hun dropdownkiezers te animeren. Door appearance: base-select te combineren met moderne CSS-animatietechnieken, zoals @starting-style en het overgangsgedrag van allow-discrete, kun je vloeiende, premium UI-overgangen maken voor elementen in de bovenste laag zonder zware JavaScript-bibliotheken te gebruiken.
Eerder was het onmogelijk om native select-dropdownmenu's te animeren, omdat de UI buiten de toegankelijke viewportbeperkingen werd gerenderd. Met appearance: base-select kun je de kiezer stijlen en animeren zoals elk ander pagina-element.
Implementeren
Zo implementeer je een geanimeerde selectiekiezer:
- Aanmelden voor aanpassing: Pas
appearance: base-selecttoe op zowel het<select>-element als het::picker(select)-pseudoelement. - Overgangen met automatisch formaat aanzetten (optioneel): Definieer
interpolate-size: allow-keywords(meestal op:root) zodat de browser kan overschakelen tussen afzonderlijke statistische waarden zoalsheight: autoenheight: 0. - De container van de bovenste laag animeren: Pas standaardstijlen voor binnenkomst/vertrek toe op
::picker(select). Als je wilt dat de overgang van de dekking werkt als je wisselt tussendisplay: noneendisplay: block, moet jetransition-behavior: allow-discretegebruiken (vaak inline geschreven alstransition: display 0.3s allow-discrete). - Koppel aan de openingsstatus met
@starting-style: Gebruik@starting-styleom de basisstijlen te definiëren die de browser moet berekenen voordat de overgang begint. Als je de tekst bijvoorbeeld wilt laten infaden, stel je de dekking in op0in het blok@starting-style. - Het icoon draaien: Gebruik pseudoelementfocus of actieve selectors zoals
:open::picker-iconom overgangen (zoals draaien of vertalen) toe te passen op de pijlindicator.
Voorbeeldcode: Smooth Select Scale and Fade
In het volgende voorbeeld zie je een aangepaste selectie met standaard pagina-animaties voor de kiezercontainer.
<!-- Always use a <label> linked via 'for' to the select for accessibility -->
<label for="theme-select">Visual Theme</label>
<select id="theme-select" class="animated-select" name="theme">
<!-- The <button> inside <select> becomes the visible trigger when appearance: base-select is used -->
<button>
<!-- <selectedcontent> automatically displays the content of the chosen <option> -->
<selectedcontent></selectedcontent>
</button>
<option value="system">
<!-- MANDATORY: Decorative inline SVGs MUST set aria-hidden="true" to prevent redundant screen reader announcement -->
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
<line x1="8" y1="21" x2="16" y2="21"></line>
<line x1="12" y1="17" x2="12" y2="21"></line>
</svg>
System Default
</option>
<option value="light">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
Light UI
</option>
</select>
/* Opt-in to customizable select */
.animated-select,
.animated-select::picker(select) {
appearance: base-select;
}
/* Enable auto-keyword transitions (usually set globally at :root) */
:root {
interpolate-size: allow-keywords;
}
/* Style the visible trigger and icon rotation */
.animated-select {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0.875rem 1rem;
font-size: 1rem;
border-radius: 8px;
cursor: pointer;
transition: border-color 0.2s ease;
}
.animated-select::picker-icon {
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.animated-select:open::picker-icon {
transform: rotate(180deg);
}
/*
* The Picker Container
* Uses top-layer animations with `allow-discrete` visibility hooks
*/
.animated-select::picker(select) {
background: white;
border-radius: 12px;
box-shadow: 0 10px 25px -3px rgba(0,0,0,0.1);
padding: 0.5rem;
margin-top: 0.25rem;
width: anchor-size(width);
overflow: hidden;
/* The crucial transition setting for popover animations */
transition:
display 0.4s allow-discrete,
overlay 0.4s allow-discrete,
opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1),
height 0.4s cubic-bezier(0.16, 1, 0.3, 1);
opacity: 0;
height: 0;
}
/* Open State */
.animated-select:open::picker(select) {
opacity: 1;
height: auto;
}
/* @starting-style to hook the transition on initial popover open */
@starting-style {
.animated-select:open::picker(select) {
opacity: 0;
height: 0;
}
}
/* Support for SVG inside Options and Selected Content */
.animated-select option svg,
.animated-select selectedcontent svg {
flex-shrink: 0; /* Prevent icons from shrinking */
width: 1.25rem;
height: 1.25rem;
}
/* MANDATORY: Provide multiple indicators (e.g. bold font and distinct background) for the checked state to avoid color-only state communication */
.animated-select option:checked {
font-weight: 700;
background-color: #f1f5f9;
}
/* Ensure copy-paste safety for users with motion sensitivities */
@media (prefers-reduced-motion: reduce) {
.animated-select::picker(select),
.animated-select::picker-icon {
transition: none !important;
}
}
Strategische implementatie en best practices
- WEL: Gebruik
@starting-styleals je animaties precies wilt activeren wanneer een element overgaat vandisplay: nonenaar zichtbaar. - Gebruik GEEN ad-hoc scrollvergrendeling. Elementen in de bovenste laag die worden beheerd door base-select, moeten natuurlijk sluitgedrag voor de achtergrond toestaan.
- WEL: Controleer de voorkeuren voor verminderde beweging. Verpak animatiebeperkingen altijd in een
prefers-reduced-motion-mediaquery om toegankelijke omgevingen te garanderen voor mensen die last hebben van bewegingsziekte. - 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.
}
});