In questo articolo faremo una sintetica rassegna di alcune tecniche utili per la personalizzazione dell'aspetto di checkbox e radio button. Ad accomunare le varie soluzioni il fatto che in nessun caso si ricorre all'ausilio di Javascript. Tutto verrà realizzato esclusivamente tramite i CSS.
Per avere da subito chiaro ciò che realizzeremo, ecco uno screenshot della prima demo, che potete peraltro testare seguendo questo link:
CSS checkbox style: la tecnica di base
Gli esempi che andremo via via analizzando e che sono disponibili in allegato per il download condividono l'approccio di base. Sono infatti tutti fondati sul cosiddetto 'checkbox hack', argomento cui abbiamo dedicato nei mesi scorsi un articolo che va considerato rispetto al tema di questo un fondamentale prerequisito. Per non appesantire questo articolo con inutili ripetizioni, ad esso rimandiamo per i dettagli tecnici.
Altra lettura vivamente consigliata è quella della lezione dedicata ai selettori di relazione della guida CSS3.
La prima applicazione di questo semplice trucchetto alla personalizzazione di checkbox e radio button è stata presentata in un tutorial di CSS Ninja. Tutte le varianti che vedremo nel nostro articolo e quelle che potrete rintracciare in rete sono di fatto derivazioni dell'esperimento di Ryan Seddon. Cerchiamo dunque di comprendere la tecnica di base.
Essenzialmente, il procedimento si compone di tre fasi:
- nel markup HTML si inserisce un input (
checkbox
oradio
) con unalabel
associata; - si nascondono i checkbox e radio button standard, ovvero quelli che presentano l'aspetto caratteristico di ciascun sistema operativo;
- si sostituiscono i controlli iniziali con controlli personalizzati nella grafica che nel loro funzionamento emulano i checkbox e i radio button.
L'immagine qui sotto chiarisce meglio il concetto:
Perché tutto possa funzionare è necessario strutturare il markup in un modo preciso. Tutti i nostri esempi hanno perciò questa configurazione nel codice HTML:
<input type="checkbox" id="html" value="HTML">
<label for="html">HTML</label>
Il checkbox o il radio button devono precedere immediatamente la label
. Quest'ultima va associata all'input corrispondente tramite l'attributo for
, che assume come valore l'id dell'input stesso.
Leggi Come personalizare e rendere accessibili Checkbox e Radio buttons.
Usare immagini per personalizzare i controlli Radio button
Le varianti che d'ora in poi analizzeremo possono essere suddivise in due categorie: quelle che sostituiscono i controlli originali con immagini e quelle che sfruttano il contenuto generato dei CSS. Iniziamo dalla prima.
La tecnica che usiamo nella prima demo è tra le più semplici.
Questo è il markup HTML:
<form action="">
<h2>Checkbox</h2>
<p>
<input type="checkbox" id="html" value="HTML">
<label for="html">HTML</label>
</p>
<p>
<input type="checkbox" id="css" value="CSS">
<label for="css">CSS</label>
</p>
<p>
<input type="checkbox" id="javascript" value="Javascript">
<label for="javascript">Javascript</label>
</p>
<h2>Radio button</h2>
<p>
<input type="radio" id="java" name="linguaggi" value="Java">
<label for="java">Java</label>
</p>
<p>
<input type="radio" id="php" name="linguaggi" value="PHP">
<label for="php">PHP</label>
</p>
<p>
<input type="radio" id="xml" name="linguaggi" value="XML">
<label for="xml">XML</label>
</p>
</form>
Nel CSS procediamo prima di tutto a nascondere i controlli standard:
input[type='radio'],
input[type='checkbox'] {
position: absolute;
clip: rect(1px, 1px, 1px, 1px);
}
Per rispettare il più possibile l'accessibilità, qui e in tutte le demo, usiamo questa soluzione basata sulla proprietà clip
al posto di display:none
.
I controlli nascosti saranno sostituiti con un'immagine iniziale ('start.png') che rappresenta i controlli disattivati. Sarà applicata come sfondo della label:
input[type='radio'] + label,
input[type='checkbox'] + label {
margin: 0;
padding: 2px 0 0px 24px;
cursor: pointer;
background: url('start.png') left center no-repeat;
}
Padding sul checkbox CSS
Oltre alla riga per la proprietà background
, è importante quella per il padding. Per lasciare spazio all'immagine/sfondo, infatti, dovremo usare un padding sinistro adeguato. Il padding sugli altri lati (specie superiore e inferiore) andrà sistemato per ottenere una resa cross-browser consistente rispetto all'allineamento e al posizionamento.
Figura 3 - Padding sinistro per fare posto all'immagine
Il cursore sarà di tipo pointer
perché l'area è cliccabile.
Ci siamo quasi. Dobbiamo ora emulare il comportamento di checkbox e radio button. Come? Quando clicchiamo e i controlli passano allo stato :checked
, l'immagine di sfondo iniziale della label sarà sostituita con quelle specifiche che sceglieremo per i radio button e per i checkbox:
input[type='radio']:checked + label {
background-image: url('radiobutton.png');
}
input[type='checkbox']:checked + label {
background-image: url('checkbox.png');
}
Tutto qui. Dobbiamo solo chiarire un aspetto importante, quello della compatibilità sui vari browser.
L'esempio funziona come è stato concepito su tutti i browser più recenti, compreso IE9. IE7 e IE8, però, non supportano la pseudoclasse :checked
. Abbiamo due modi per risolvere:
- usare una libreria Javascript come Selectivizr per estendere il supporto della pseudoclasse su questi browser;
- adottare una strategia di graceful degradation.
Abbiamo scelto quest'ultima via: su IE7 e IE8 il form sarà perfettamente funzionante ma i controlli avranno l'aspetto standard, non personalizzato, come dimostra questo screenshot:
Figura 4 - Resa del form su IE8
Per implementare il tutto è sufficiente comporre tutte le dichiarazioni CSS che riguardano la personalizzazione dei controlli in questo modo:
p:not(#foo) > input[type='radio'],
p:not(#foo) > input[type='checkbox'] {
position: absolute;
clip: rect(1px, 1px, 1px, 1px);
}
p:not(#foo) > input[type='radio'] + label,
p:not(#foo) > input[type='checkbox'] + label {
[...]
}
[...]
Dato che IE7 e IE8 non supportano nemmeno la pesudoclasse :not
, non interpreteranno le regole precedute da p:not(#foo) >
. Questo piccolo snippet va adeguato al vostro markup. Nel nostro caso si è usato p
perché gli input sono inseriti in un paragrafo. Se li racchiudete in un div scriverete:
div:not(#foo) > input[type='radio']
Quindi: si prende l'elemento che contiene gli input, si associa ad esso la pseudoclasse :not
, si usa come valore un id non usato in nessuna parte della pagina (nell'esempio #foo
, ma può essere pure #blahblahblah
). Tutti gli esempi usano questa tecnica.
Usare gli sprite
Nella prima demo abbiamo usato per implementare la tecnica tre immagini singole come sfondo. Nulla vieta di usare uno sprite come avviene nel secondo esempio:
La differenza con la prima tecnica consiste nel fatto che quando i controlli sono attivati non si modifica l'immagine di sfondo, ma la posizione dello sfondo in base alla configurazione dello sprite usando la proprietà background-position
:
Questo il codice CSS (nell'esempio abbiamo sfruttato solo alcuni stati):
p:not(#foo) > input[type='checkbox'] + label {
background: url('sprite.png') 0 0;
}
p:not(#foo) > input[type='checkbox']:checked + label {
background-position: 0 -60px;
}
p:not(#foo) > input[type='radio'] + label {
background: url('sprite.png') 0 -120px;
}
p:not(#foo) > input[type='radio']:checked + label {
background-position: 0 -180px;
}
Adoperando gli sprite si guadagna su un versante (una sola immagine invece che diverse), ma si deve porre più attenzione all'allineamento tra la label e il controllo. Ecco la regola che abbiamo dovuto scrivere per posizionare e allineare al meglio l'immagine/sfondo della label rispetto al testo:
p:not(#foo) > input[type='checkbox'] + label, /* Stili per le label */
p:not(#foo) > input[type='radio'] + label {
display: inline-block; /* Display */
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box; /* Border-box */
width:30px; /* Larghezza della label = all'immagine */
height:30px; /* Altezza della label = all'immagine */
padding: 8px 0 6px 30px; /* Padding per lasciare spazio allo sfondo (a sinistra) e per posizionare al meglio il testo rispetto al controllo */
cursor:pointer; /* Imposta il cursore */
}
I commenti al codice sono sufficienti per rendere chiara la funzione di ciascuna regola.
La questione del posizionamento e dell'allineamento è in effetti uno degli aspetti più insidiosi da affrontare sfruttando questa tecnica (ma in generale nella strutturazione dei form con i CSS). Non solo si deve giocare quasi sempre a livello di pochi pixel, ma ci si scontra sovente con piccole inconsistenze nella resa tra i vari browser che rendono necessario testare sempre con cura per evitare differenze macroscopiche.
Nello specifico della tecnica di cui si parla in questo articolo, molte soluzioni reperibili in rete usano uno span
aggiuntivo all'interno della label
nel markup HTML come appoggio per i CSS, con la finalità di rendere meno complicata la gestione dei controlli e del loro posizionamento. Il codice HTML assume tipicamente questa forma:
<input type="checkbox" id="html" value="HTML">
<label for="html"><span></span> HTML</label>
Su questo approccio è basato il terzo dei nostri esempi.
Usare il contenuto generato al posto delle immagini
In alternativa alle immagini, possiamo usare per personalizzare checkbox e radio button il contenuto generato dei CSS, ovvero gli pseudoelementi :before
e :after
.
Il procedimento è identico a quello visto per le immagini, ma la grafica di checkbox e radio button viene creata sfruttando unicamente i CSS:
Figura 7 - Checkbox creato con il contenuto generato
Il quadratino giallo, il bordo rosso, il segno di spunta blue che vedete nell'immagine qui sopra sono puro CSS.
Partiamo dunque con l'analisi del quarto esempio.
Dopo aver nascosto nel solito modo i controlli originali, procediamo inserendo poche regole generali per le label:
input[type='checkbox'] + label, /* Stili per le label */
input[type='radio'] + label {
cursor: pointer; /* Imposta il cursore per le label */
padding: 0 0 0 15px; /* Padding a sinistra per rendere il controllo cliccabile */
margin: 0 0 0 5px; /* Margine a destra per posizionare al meglio il controllo */
position: relative; /* Creiamo un contesto di posizionamento per il contenuto generato */
}
Al solito si lavora sul padding sinistro, questa volta però per rendere il nuovo controllo cliccabile. Il valore deve essere pari o superiore alla larghezza del contenuto generato che inseriremo.
Visto che in questa demo useremo il posizionamento assoluto per collocare nel punto giusto il contenuto generato, impostiamo per le label il posizionamento relativo.
Il valore del margine sinistro (qui 5px) corrisponde in positivo a quello che useremo più avanti per il posizionamento a sinistra (-5px).
Si prosegue con l'inserimento del checkbox sostitutivo come contenuto generato:
p:not(#foo) > input[type='checkbox'] + label:before { /* Contenuto generato per le label associate ai checkbox */
content: "";
width: 15px;
height: 15px;
border: solid 1px #222;
background: #fff;
cursor: pointer;
position: absolute; /* Posizioniamo il contenuto generato rispetto alla label */
top: -1px; /* Spostamento verticale */
left: -5px; /* Spostamento orizzontale */
}
Cosa abbiamo fatto? Nient'altro che creare un quadrato largo e alto 15px, con un bordo sottile di 1px, lo sfondo bianco, il cursore a puntatore, posizionato assolutamente rispetto alla label. È il quadratino che rappresenta il checkbox disattivato.
Ora dobbiamo ricreare l'effetto dell'attivazione del checkbox.
Per inserire il segno di spunta sfruttiamo lo pseudo-elemento :after
:
p:not(#foo) > input[type='checkbox']:checked + label:after { /* Segno di spunta dei checkbox e suo colore */
content: "2714";
display: block; /* Display: block per centrare il segno di spunta orizzontalmente */
position: absolute; /* Posizioniamo il segno di spunta rispetto alla label */
top: 1px;
left: 0;
margin: 0 auto; /* Centriamo orizzontalmente il segno di spunta */
color: #222; /* Colore del segno di spunta */
font-size: 11px; /* Dimensione del carattere */
}
Il contenuto (content
) sarà il carattere speciale ✓, che va indicato nel CSS con il codice 2714
(qui un'utile tabella con i caratteri e i codici Unicode associati).
La dimensione del font e il suo colore saranno adattate al contesto e alle nostre esigenze di personalizzazione. Per centrare orizzontalmente il segno di spunta lo dichiariamo display: block
e usiamo poi margin: auto
a sinistra e destra. Il posizionamento sull'asse verticale all'interno del quadratino vuoto lo definiamo con la proprietà top
.
Per quanto riguarda i radio button, il procedimento è analogo. Prima creiamo con :before
un cerchio vuoto (grazie a border-radius
):
p:not(#foo) > input[type='radio'] + label:before { /* Contenuto generato per le label associate ai radio button */
content: "";
width: 15px;
height: 15px;
border: solid 1px #222;
-webkit-border-radius: 7.5px;
border-radius: 7.5px; /* Border-radius: 7.5px = metà di larghezza e altezza */
background: #fff;
cursor: pointer;
position: absolute; /* Posizioniamo il contenuto generato rispetto alla label */
top: -1px; /* Spostamento verticale */
left: -5px; /* Spostamento orizzontale */
}
Poi passiamo al segno di spunta. Potremmo usare un carattere speciale come nel primo caso, ma ispirandoci ai form customizzati del framework Foundation (la loro soluzione ricorre a Javascript), abbiamo qui implementato una soluzione particolare:
p:not(#foo) > input[type='radio']:checked + label:after { /* Segno di spunta dei radio button e suo colore */
content: "";
display: block; /* Display: block per centrare il segno di spunta orizzontalmente */
width: 8px; /* Creiamo un cerchio più piccolo */
height: 8px; /* Anche in altezza */
-webkit-border-radius: 4px;
border-radius: 4px;
position: absolute;
top: 3px;
left: 0;
margin: 0 auto;
background-color: #222; /* Colore di sfondo */
}
Invece di inserire un carattere speciale, tipo un pallino o un bullet, il contenuto generato è rappresentato da un cerchio più piccolo di qualche pixel rispetto al primo (8px vs. 14px), con un colore di sfondo diverso. Occhio anche in questo caso al posizionamento.
Varianti con il contenuto generato
Il funzionamento dell'approccio basato sul contenuto generato dovrebbe a questo punto essere chiaro. Su questa base si possono creare diverse varianti che differiscono dalla prima per le modalità di posizionamento/allineamento e per gli effetti grafici.
La prima variante non usa il posizionamento assoluto per sistemare i controlli e usa come segno di spunta per i radio button il carattere ●.
La seconda fa ricorso ad uno span aggiuntivo.
Per personalizzare l'aspetto dei controlli si agirà soprattutto sui colori (dello sfondo, dei bordi, del segno di spunta):
Figura 8 - Controlli personalizzati: varianti cromatiche
Ecco l'esempio 5, nella variante basata sul posizionamento assoluto e in quella che fa uso dello span aggiuntivo.
Infine, per quanti possano pensare che il contenuto generato rappresenti un sacrificio in termini di aspetto grafico, ecco la demo conclusiva. Basta aggiungere un'ombreggiatura adeguata per migliorare ulteriormente l'aspetto di checkbox e radio button, che hanno ora un aspetto più realistico:
box-shadow: 0 1px 2px rgba(0,0,0,0.20),
inset 0px -15px 10px -12px rgba(0,0,0,0.20);
Una nota conclusiva
Prima di chiudere una nota sulla resa degli esempi (e quindi di questa tecnica) sui dispositivi mobili, nello specifico quelli basati su iOS. Nessun problema sugli iPhone e gli iPad con iOS 6. Ma su iOS 5 e precedenti il click sulla label non produce effetti, rendendo di fatto gli input non attivabili. Per risolvere basta aggiungere l'evento onclick
sulle label in questo modo:
<label for="javascript" onclick="">Javascript</label>
Lo abbiamo fatto solo nel primo esempio, ma la soluzione è questa.