La proprietà CSS appearance: base-select consente di applicare uno stile a un elemento <select> e al relativo selettore (tramite ::picker(select)) come i normali elementi HTML.
In combinazione con le moderne tecniche di layout CSS come Grid o Flexbox, puoi abbandonare il tradizionale elenco verticale impilato e creare un'esperienza visiva ricca senza JS personalizzato.
Ad esempio, puoi utilizzare display: grid per applicare uno stile alle opzioni come una griglia 2D di schede.
Questa opzione è ideale per selettori colori, selettori di emoji o varianti del prodotto in cui un menu visivo dettagliato è più efficace di un elenco.
Modalità di implementazione
1. Attiva lo stile di base
Applica appearance: base-select sia all'elemento <select> sia al suo pseudo-elemento di selezione interno select::picker(select):
select.grid-picker {
&, &::picker(select) {
appearance: base-select;
}
}
2. Definisci lo stile del contenitore del selettore
Scegli come target select:open::picker(select) e applica display: grid (o display: flex).
Definisci le colonne e gli spazi vuoti come faresti per qualsiasi contenitore.
select.grid-picker:open::picker(select) {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
IMPORTANTE: il :open è necessario. Se applichi uno stile a select::picker(select), il selettore sarà visibile anche quando l'elemento select è chiuso.
3. Definisci lo stile delle opzioni
Scegli come target gli elementi <option> per applicare uno stile come elementi o schede della griglia.
Puoi aggiungere immagini, SVG o layout complessi al loro interno.
/* Style options as grid cards */
.grid-picker option {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
/* padding, border-radius, background, etc. */
}
4. Definisci lo stile dell'opzione selezionata
Per applicare uno stile all'opzione selezionata, utilizza option:checked. Ad esempio:
.grid-picker option:checked {
position: relative;
border: 2px solid var(--primary-color);
background-color: oklab(from var(--primary-color) l a b / 8%);
}
Come per qualsiasi controllo UI, non comunicare lo stato solo con il colore e utilizza più indicatori visivi (ad es. colori, spessore del bordo, segno di spunta) per comunicare l'opzione selezionata.
5. (Facoltativo) Personalizza lo stile del segno di spunta dell'opzione selezionata
Per impostazione predefinita, il browser visualizza un segno di spunta come primo elemento secondario di ogni <option>.
Si comporta come un ::before con content: "✓" / "", visualizzato prima di un ::before fornito dall'autore, se esistente.
Se l'opzione non è selezionata, il segno di spunta viene nascosto tramite visibility: hidden.
Puoi applicare uno stile utilizzando lo pseudo-elemento ::checkmark o nasconderlo completamente se vuoi comunicare la selezione solo tramite lo stile o utilizzare un indicatore personalizzato.
Ad esempio, per posizionarlo nell'angolo in alto a destra dell'opzione, puoi utilizzare un codice simile al seguente:
select.grid-picker {
> option::checkmark {
/* Hide the checkmark for unselected options */
display: none;
}
> option:checked {
position: relative;
&::checkmark {
position: absolute;
inset-block-start: -.4em;
inset-inline-end: -.5em;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
min-inline-size: 1.6em;
aspect-ratio: 1;
padding-right: 0.12em;
padding-left: 0.05em;
border-radius: 50%;
background: var(--primary-color);
color: white;
font-size: 70%;
font-weight: bold;
}
}
}
Suggerimento: quando disegni un contenitore (ad es. un cerchio) attorno al segno di spunta, spostalo leggermente verso l'alto (come abbiamo fatto qui con padding-bottom e padding-left) per un centraggio ottico che tenga conto della sua forma.
NON utilizzare padding-inline-start o padding-block-end per questo scopo, poiché cambiano a seconda della modalità di scrittura, mentre la forma del segno di spunta non cambia.
6. (Facoltativo) Personalizza la visualizzazione dell'opzione selezionata
Per impostazione predefinita, <select> mostrerà una versione di solo testo dell'opzione selezionata.
Per visualizzare contenuti avanzati per il valore selezionato, utilizza <selectedcontent> all'interno di un <button> e/o per visualizzare l'opzione selezionata in modo diverso (ad esempio, nascondi determinati dettagli).
Non utilizzare NOT <selectedcontent> senza <button>, altrimenti <selectedcontent> diventerà semplicemente parte del selettore, come qualsiasi altro elemento dei contenuti.
Best practice
- Testa il comportamento del layout. L'impostazione
appearance: base-selectrimuove il comportamento predefinito del browser di ridimensionare l'elemento select in base alla larghezza dell'opzione più lunga. Potresti dover impostare una larghezza fissa o utilizzare vincoli flex/grid per evitare variazioni del layout. - Assicurati che il tuo
<select>abbia un attributonamee un<label>associato. In questo modo, anche con una UI personalizzata, il componente rimane accessibile agli screen reader e funziona correttamente con gli invii di moduli standard. - Utilizza gli attributi
valuestandard sulle opzioni per assicurarti che l'invio del modulo funzioni esattamente come prima.
Accessibilità
Un elemento <select> nativo gestisce solo la navigazione lineare con le frecce Su/Giù, quindi le frecce Sinistra/Destra non fanno nulla nel selettore aperto e le frecce Su/Giù continuano a navigare tra gli elementi in sequenza, indipendentemente dalle righe e dalle colonne.
Per implementare la navigazione spaziale che segue la griglia, puoi intercettare keydown e indirizzare lo stato attivo all'opzione più vicina nella direzione premuta (ad esempio utilizzando getBoundingClientRect()) quando vengono utilizzati i tasti freccia.
Per determinare l'elemento successivo/precedente appropriato, utilizza i metodi DOM che operano sulla geometria effettiva (ad es. getBoundingClientRect()) anziché analizzare stringhe CSS come grid-template-columns o grid-column, che possono essere fragili e soggette a errori per griglie complesse, sottogriglie, RTL e altri scenari di layout.
Detto questo, non esagerare con l'overengineering; utilizza gli invarianti che sai essere veri per il layout per semplificare questa logica.
Nota: questo riguarda solo la navigazione da tastiera. <select> e ::picker() hanno ancora i loro ruoli predefiniti (menu o listbox), che non possono essere sostituiti, né possono contenere un elemento con role="grid".
La nidificazione di un wrapper con role="grid" all'interno del selettore funziona in pratica, ma non è valida e potrebbe sorprendere gli utenti di TA.
In alcuni casi, l'utilizzo di <optgroup> per raggruppare le opzioni può produrre una buona UX dello screen reader senza role="grid".
In caso contrario, potrebbe essere necessario passare a una soluzione personalizzata utilizzando l'API Popover, il posizionamento degli ancoraggi e role="grid" per ottenere l'esperienza utente desiderata per gli utenti di screen reader.
Strategia di riserva
Opzioni di riserva e supporto dei browser per <select> personalizzabile
Per i browser che non supportano ancora appearance: base-select, l'elemento <select> si riduce correttamente a un menu a discesa del sistema operativo standard.
- Contenuti non testuali ignorati: i browser meno recenti rimuovono i tag HTML (come
<svg>o<div>) all'interno dei tag<option>ed eseguono il rendering solo dei nodi di testo. Assicurati che i contenuti testuali di<option>siano leggibili e significativi di per sé. - Gestione della struttura HTML: i parser standard potrebbero ignorare i tag
<button>e<selectedcontent>all'interno di<select>o trattarli come non validi. Per il potenziamento progressivo non sono strettamente necessari polyfill JavaScript pesanti se consideri il testo standard come un fallback leggibile.
document.addEventListener("DOMContentLoaded", () => {
// Check if browser supports base-select value
if (!CSS.supports("appearance", "base-select")) {
// Custom select overrides are not supported natively.
}
});