Frontend

Dare stile ad un form con CSS

Autore

Manuel Ricci

Ci sono dei tag HTML che vengo utilizzati più spesso rispetto ad altri e quando si parla di un sito web è assai difficile che, almeno una volta, non venga utilizzato il tag form con i suoi vari widget.

I form sono stati introdotti per la prima volta nel 1995, all’epoca HTML era alla versione 2.

Io li chiamo form, ma sono anche chiamati form widget, elementi del form o più genericamente input. Di come si usano e delle loro caratteristiche ne ho già ampiamente discusso nella lezione dedicata ai form nel corso di HTML.

Tornando alla storia, siamo nel 1995 quando i form fanno la loro comparsa, ma CSS venne rilasciato per la prima volta nel 1996 e la sua adozione non fu immediata. Ci vollero anni prima di avere un’adozione di base uniforme.

I produttori dei browser inizialmente erano riluttanti nel permettere di applicare degli stili ai form, ma le cose fortunatamente cambiarono, ma ancora oggi abbiamo delle limitazioni.

Per questo quindi durante questo approfondimento parleremo di input facili da personalizzare e input “brutti”, quest’ultimi sono quelli che più di tutti ci fanno disperare.

Input facili da personalizzare

Come da titolo, in questa sezione vedremo quali sono i tag dei form facilmente personalizzabili. Nello specifico abbiamo:

  • <form>
  • <fieldset> e <legend>
  • <input />
  • <textarea>
  • <input type="button|submit|reset" /> e <button>
  • <label>
  • <output>

Ipotizziamo di avere un form di questo tipo:

1<form action="" autocomplete="off">
2  <div class="input-group">
3    <label for="nome">Nome</label>
4    <input type="text" name="nome" id="nome" required />
5  </div>
6  <div class="input-group">
7    <label for="cognome">Cognome</label>
8    <input type="text" name="cognome" id="cognome" required />
9  </div>
10  <div class="input-group">
11    <label for="email">Email</label>
12    <input type="email" name="email" id="email" required />
13  </div>
14  <div class="input-group">
15    <label for="servizio">Servizio</label>
16    <select name="servizio" id="servizio">
17      <option value="servizio 1">Servizio 1</option>
18      <option value="servizio 2">Servizio 2</option>
19      <option value="servizio 3">Servizio 3</option>
20      <option value="servizio 4">Servizio 4</option>
21      <option value="servizio 5">Servizio 5</option>
22    </select>
23  </div>
24  <div class="input-group">
25    <label for="data-appuntamento">Data appuntamento</label>
26    <input type="date" name="data-appuntamento" id="data-appuntamento" required="required">
27  </div>
28  <div class="input-group">
29    <label for="messaggio">Messaggio</label>
30    <textarea name="messaggio" id="messaggio" cols="30" rows="10"></textarea>
31  </div>
32  <input type="submit" value="Invia">
33</form>

Qui ho mischiato un po’ di campi “facili” e “brutti” (<select>), l’output è questo

risultato del form HTML

Possiamo decisamente migliorarlo con CSS:

1button,
2input,
3select,
4textarea {
5  font-family: inherit;
6  font-size: 100%;
7  width: 360px;
8  padding: 0;
9  margin: 0;
10}
11
12label {
13  display: block;
14  margin-bottom: 4px;
15}
16
17.input-group {
18  margin-bottom: 16px;
19}

Il risultato è sicuramente più carino, ma c’è un piccolo problema. L’occhio meno allenato non lo nota, ma notate qualcosa di strano tra gli <input /> e la <select>? Vi dò un indizio

differenze tra input

La select è leggermente più piccola, perché ogni tag dei form gestisce in maniera diversa il box-sizing, possiamo migliorare immediatamente aggiungendo box-sizing: border-box

1input,
2select,
3textarea {
4  font-family: inherit;
5  font-size: 100%;
6  width: 360px;
7  padding: 0;
8  margin: 0;
9  box-sizing: border-box;
10}

Questo modificherà il modo in cui il browser calcola la dimensione del box. Vorrei far notare che difficilmente in un caso reale sarà necessario inserire box-sizing su selettori così specifici (eh già anche se si tratta di selettori di elemento).

Sarà decisamente più comune trovarsi davanti ad una regola del genere:

1*, *::before, *::after {
2   box-sizing: border-box;
3}

Oppure

1html {
2   box-sizing: border-box;
3}
4
5*, *::before, *::after {
6   box-sizing: inherit;
7}

Sono tutti e due metodi validi.

Aggiungiamo giusto qualche riga in più di CSS per completare il nostro form.

1input,
2select,
3textarea {
4  font-family: inherit;
5  font-size: 100%;
6  width: 360px;
7  padding: 0;
8  margin: 0;
9  box-sizing: border-box;
10  padding: 8px;
11  border-radius: 5px;
12  border: 1px solid #aaa;
13}
14
15input[type="submit"] {
16  width: fit-content;
17  background-color: tomato;
18  border-color: tomato;
19  padding: 8px 16px;
20}

Molto bene! Ma… Perché c’è sempre un ma.

differenze tra browser

Notate niente di strano? Tra Firefox e Chrome su Windows 11 ci sono delle differenze tra alcuni input. Guarda caso è proprio l’input “brutto” ad essere differente. Questo ci fa capire una cosa importante: ancora oggi è necessario controllare le nostre pagine web su più browser.

Per correggere basta aggiungere background-color: #fff o background-color: transparent per poter risolvere il “bug”.

Se quelli elencati prima sono gli elementi facilmente personalizzabili, ce ne sono alcuni che lo sono, ma farlo non è proprio così intuitivo. Parliamo quindi di elementi difficili da personalizzare

Elementi difficile da personalizzare

Ammetto che prima ho “mentito” dicendo che <input /> è un tag facilmente personalizzabile, era più corretto infatti scrivere che la maggior parte degli <input /> sono facilmente personalizzabili. Ce ne sono due che non sono proprio intuitivi in termini di personalizzazione e sono type=”checkbox” e type=”radio”.

Dato che i browser mostrano in maniera diversa alcuni input. Possiamo provare ad utilizzare la proprietà appearance: none per rimuovere gli stili dello user agent, ma con checkbox e radio questi scompaiono.

La loro scomparsa dalla pagina però non deve farci disperare perché possiamo ricrearli con qualche riga di CSS.

Per i checkbox possiamo scrivere qualcosa del genere:

1input[type="checkbox"] {
2  appearance: none;
3  position: relative;
4  width: 1em;
5  height: 1em;
6  border: 1px solid gray;
7  vertical-align: -2px;
8}
9
10input[type="checkbox"]::before {
11  content: "✓";
12  position: absolute;
13  font-size: 1.2em;
14  right: -1px;
15  top: -0.3em;
16  visibility: hidden;
17}
18
19input[type="checkbox"]:checked::before {
20  visibility: visible;
21}
22
23input[type="checkbox"]:disabled {
24  border-color: black;
25  background: #ddd;
26  color: gray;
27}

Unici tre appunti da fare su quanto scritto:

  • vertical-align: -2px ci serve per poter sistemare l’allineamento verticale alla baseline del testo, il quale altrimenti risultava più alto.
  • visibility: hidden e visibility: visible li usiamo al posto della proprietà display per evitare ricalcoli del layout
  • Abbiamo usato le pseudo classi :checked e :disabled per modificare lo stile della checkbox in base al suo stato.

Mentre con i radio button possiamo fare qualcosa di simile:

1input[type="radio"] {
2  appearance: none;
3  width: 20px;
4  height: 20px;
5  border-radius: 10px;
6  border: 2px solid gray;
7  vertical-align: -2px;
8  outline: none;
9}
10
11input[type="radio"]::before {
12  display: block;
13  content: " ";
14  width: 10px;
15  height: 10px;
16  border-radius: 6px;
17  background-color: red;
18  font-size: 1.2em;
19  transform: translate(3px, 3px) scale(0);
20  transform-origin: center;
21  transition: all 0.3s ease-in;
22}
23
24input[type="radio"]:checked::before {
25  transform: translate(3px, 3px) scale(1);
26  transition: all 0.3s cubic-bezier(0.25, 0.25, 0.56, 2);
27}

Qui, ammetto di essermi spinto un po’ oltre con le personalizzazioni, ma il focus lo dobbiamo mantenere su input[type="radio"] e input[type="radio"]::before abbiamo creato dei cerchi e colorati a piacimento, il resto per il momento è superfluo e serve solo a dare un po’ più di stile e flexare sulle capacità di CSS.

Elementi “brutti” da personalizzare

Ne abbiamo già incontrato uno di elemento “brutto” ed è <select>, ma non è il solo, oltre a lui ci sono:

  • <datalist>
  • <input type=”datetime-local” />
  • <input type=”range” />
  • <input type=”color” />
  • <input type=”file” />
  • <progress>
  • <meter>

Partiamo dal primo, <datalist> questo è praticamente impossibile da personalizzare. Ciò che fa è mostrare una lista di valori consigliati, ma con CSS possiamo farci veramente poco.

Tutti gli input temporali come datetime-local, time, week e month non danno la possibilità di cambiare stile al date o time picker.

Per range possiamo intervenire sulla barra con qualche proprietà come:

1appearance: none;
2background: red;
3height: 2px;
4padding: 0;
5outline: 1px solid transparent;

Ma decisamente più difficile è la personalizzazione del pallino da trascinare. Per poterlo personalizzare c’è bisogno di usare regole non standardizzate e specifiche per ogni browser.

color non è poi così malaccio, ma la personalizzazione è limitata alla rimozione di bordi e padding, fine. Per fare personalizzazioni più importanti bisognerà ricorrere a soluzioni differenti.

Anche file non è così malaccio, ma per poter ottenere qualcosa di più personalizzato bisogna usare <label> come bottone, nascondendo l’input in questo modo:

1input[type="file"] {
2  height: 0;
3  padding: 0;
4  opacity: 0;
5}
6
7label[for="file"] {
8  box-shadow: 1px 1px 3px #ccc;
9  border: 1px solid rgb(169, 169, 169);
10  border-radius: 5px;
11  text-align: center;
12  line-height: 1.5;
13}

<progress> e <meter> sono i peggiori di tutta la banda e si tende ad usare soluzioni completamente personalizzate già dal principio, come feci vedere in un tutorial in due puntate su TikTok, se vuoi vedere come implementare un <meter> personalizzato guardati “come fare un controllore di password animato parte 1” e “come fare un controllore di password animato parte 2”.

Pseudo classi degli elementi dei form

Per non farci mancare niente abbiamo ben 20 pseudo classi disponibili per personalizzare ulteriormente i nostri input.

Nello specifico:

  • :hover: seleziona un elemento quando viene passato sopra il mouse
  • :focus: seleziona un elemento quando questo è in stato di focus (quando viene cliccato o raggiunto dal tab con la tastiera)
  • :active: seleziona un elemento nel mentre che viene cliccato (quando il pulsante del mouse è premuto)
  • :required: seleziona un elemento con attributo required
  • :optional: seleziona un elemento senza attributo required
  • :valid: seleziona un elemento che risulta aver passato i controlli di validazione impostati con HTML
  • :invalid: seleziona un elemento che non ha passato i controlli di validazione impostati con HTML
  • :in-range: come per :valid ma per i campi numerici
  • :out-of-range: come per :invalid ma per i campi numerici
  • :enabled: seleziona un elemento editabili
  • :disabled: seleziona un elemento disabilitati con l’attributo disabled
  • :read-only: seleziona un elemento in sola lettura (con attributo readonly)
  • :read-write: seleziona un elemento editabili.
  • :checked: seleziona un elemento (radio e checkbox) che sono stati flaggati
  • :indeterminate: seleziona un elemento (solo checkbox) che ha stato indeterminato (linea orizzontale)
  • :default: seleziona un elemento con attributo checked impostato
  • :focus-within: seleziona l’elemento che è in stato di focus ho contiene un elemento in tale stato.
  • :focus-visible: seleziona un elemento che ha ricevuto il focus via tastiera, invece che da mouse. Utile per differenziare gli stili.
  • :placeholder-show: seleziona un elemento che ha il placeholder visibile.
  • :user-invalid: al momento della scrittura di questo approfondimento questa pseudo classe è disponibile solo in Firefox, ma sarà presto disponibile su tutti i browser e funziona in maniera molto simile a:invalid`, ma fornisce un’esperienza utente finale migliore.

Caricamento...

Diventiamo amici di penna? ✒️

Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.

Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!

Ho in previsione di mandarti una newsletter ogni due settimane e una commerciale quando capita.