CSS Battle – 3 settembre 2025

Nuovo episodio della CSS Battle, un modo divertente di tenersi in allenamento e rinfrescare qualche concetto sul front-end development. La figura di oggi ci permette di ripassare proprietà come display: grid; clip-path e stacking context. Direi che abbiamo un bel po' di carne al fuoco.

Set 3, 2025 | Web Development

Benvenuti in un nuovo episodio della CSS Battle (https://cssbattle.dev/), dove analizziamo la figura proposta dal sito e la traduciamo in HTML/CSS, facendo particolare attenzione a capire ogni passaggio.
Per chi non conoscesse la CSS Battle, riporto un piccolo paragrafo tratto da un articolo precedente.

CSS Battle è la palestra digitale per chi si occupa di front-end development. Gli addetti ai lavori sanno bene che l'unico modo di imparare il codice è scriverlo, scriverlo e scriverlo ma non sempre si hanno dei progetti interessati sotto mano per apprendere ed evitare i soliti banali esercizi. Nasce così CSS Battle, che ci pone di fronte a una divertente sfida: ogni giorno viene pubblicata una figura, una forma, da replicare utilizzando l'HTML/CSS tramite la loro piattaforma online. Tutto qua, semplice semplice. Ci viene fornito un template di base con colori e dimensioni del quadro per riferimento, il resto tocca a noi.
In realtà l'obbiettivo reale, la vera challenge, sarebbe rifare la figura con il minor numero di caratteri possibili. Tuttavia non è mio intento in questa sede approfondire l'argomento, anche perché spesso si tratta di piccoli espedienti come minimizzare il codice o utilizzare determinati selettori.

Penso sia interessante pubblicare in questo blog, saltuariamente, qualche Battle che metta in luce l'utilizzo di una regola in una particolare situazione, accompagnata dal codice con relativa spiegazione e motivazione delle scelte. Potrà essere utile da un lato ai già esperti per rinfrescare alcuni concetti, dall'altro ai semplici curiosi che vogliono capire il dietro le quinte dello sviluppo di una interfaccia (perlomeno le basi).

aldo giovanni giacomo pignolo
La soluzione non è unica

La soluzione che vi propongo è una delle possibili. Effettuerò delle scelte con una determinata intenzione, ma ciò non significa che la strada per raggiungere l'obbiettivo sia univoca, anzi. Il fascino del CSS è proprio la possibilità di offrire moltissime regole che impostano il progetto diversamente ma perseguono lo stesso scopo. Ai colleghi consiglio di provare a elaborare la propria soluzione.

Dritti al punto: analisi della forma

Figura 1 - Le nostre armi per questa battaglia: un campo di testo a sinistra, la preview di ciò che facciamo al centro e la figura da copiare a destra.

Saltiamo la parte in cui descriviamo l'interfaccia del sito (se volete una spiegazione un minimo più approfondita vi rimando a questo articolo). In estrema sintesi abbiamo a sinistra il nostro editor di testo dove possiamo scrivere HTML e CSS -dentro il tag style ovviamente, al centro un riquadro che traduce direttamente il codice in forma e più a destra la figura che dobbiamo copiare. A fianco alla scritta "Code output" abbiamo due utili opzioni, Slide&Compare e Diff che ci aiutano a fare il confronto tra la forma originale e il nostro lavoro tramite rispettivamente uno slider comparativo e un metodo di fusione differenza. Per capirne il funzionamento provate ad attivarne uno alla volta o tutti e due insieme

Andiamo dritti al punto focale dell'articolo: la forma. Si tratta di una sorta di "mirino", centrato nel nostro riquadro, che ricordiamo misura 400x300px come riportato in alto a destra. Il primo passo da fare è scomporre la composizione in figure elementari, in modo tale da costruire il nostro codice con geometrie semplici da gestire.

Figura 2 - La composizione è centrata rispetto al riquadro.

Nella grafica possiamo riconoscere 4 figure elementari:

  • una circonferenza esterna, di colore bianco;
  • un cerchio pieno interno, più piccolo, al centro dell'intera composizione
  • due rettangoli, sovrapposti alle prime due figure, uno orizzontale e uno verticale, azzurre come lo sfondo

La mia interpretazione è chiara. Per semplificare il codice la mia proposta vede i gap della circonferenza e del cerchio come due rettangoli che si sovrappongono e sono dello stesso colore dello sfondo. Immaginate una serie di layer che si sovrappongono: sopra un piano azzurro abbiamo la circonferenza, poi il cerchio e sopra ancora i due rettangoli, dispositi a 90° l'uno rispetto all'altro di colore azzurro. Il colore uguale allo sfondo permette di percepire l'area dei rettangoli come porzioni escluse dalla figura. Ho già chiarito, nel riquadro poco più in alto, che questo è uno dei tanti tipi di approccio, sentitevi liberi di sperimentarne altri.

Figura 3 - La figura è composta da una circonferenza, un cerchio e due rettangoli sovrapposti. I due rattangoli possono essere percepiti come aree di esclusione di porzioni di figura.

Mettiamo mano al codice: reset

Cominciamo subito a scrivere la base del nostro codice. Innanzitutto un piccolo reset, non necessario ma utile per ripassare anche questo aspetto. Ogni browser ha delle regole CSS di base che applica quando non ci sono altre regole specifiche che le sovrascrivono. Di solito quando si inizia un nuovo progetto è utile applicare un "reset", ovvero un insieme di regole che annulla gli stili di default dei vari browser. Per approfondire date un'occhiata a questo articolo di Geek for Geeks.

Nel nostro caso vediamo che il rettangolo arancione è staccato dai bordi del riquadro. Togliamo quei margini aggiungendo semplicemente la regola margin: 0 al body in questo modo:

<div></div>
<style>
body {
  margin: 0;
}

div {
  width: 100px;
  height: 100px;
  background: #dd6b4d;
}
</style>

Due problemi: overlap e centratura

Una volta risolta la questione reset, impostiamo subito l'impianto di base del nostro progetto. Abbiamo detto che l'intera figura è centrata rispetto al riquadro e come abbiamo visto in questo articolo ci sono tanti modi di farlo. Ma attenzione: questa volta è necessario fare una considerazione in più. Le figure elementari che andremo a inserire sono tutte centrate e sovrapposte tra loro. Questo significa che dobbiamo scrivere un codice che posizioni ogni div al centro del riquadro, cosa che non accade quando ad esempio usiamo un semplice display: flex perché in tal caso ogni nuovo div si disporrà a fianco al precedente. Neanche il solo display: grid è sufficiente, perché sappiamo che ogni nuovo div occuperà una nuova cella della nostra griglia, a seconda di come abbiamo definito rows e columns. Ma display: grid effettivamente può esserci d'aiuto, basta utilizzarlo nel modo giusto.

Cominciamo dando al body un display: grid nudo e crudo. Sappiamo che di base un comando del genere, assegnato al parent (nel nostro caso il body) dispone i child incolonnati; in altre parole ogni child occupa una row diversa nella stessa column. A noi però interessa che tutti i child siano sovrapposti e non incolonnati. Possiamo raggiungere questo obbiettivo facendo in modo che tutti i child occupino la stessa cella, quindi stessa row e stessa column. Il codice modificato è questo:

<div></div>
<style>
body {
  margin: 0;
  display: grid;
}

body * {
  grid-row: 1;
  grid-column: 1;
}

div {
  width: 100px;
  height: 100px;
  background: #dd6b4d;
}
</style>

Il selettore * è un selettore universale, seleziona tutti i tag nidificati nel body. Occorre prestare attenzione quando lo si usa in contesti reali e in progetti più complessi ma nel nostro caso è una utile scorciatoia per selezionare tutti i child del body. Le regole grid-row: 1 e grid-column: 1 impongono ai div child di posizionarsi tutti nella cella (1,1) quindi rispettivamente nella prima riga e nella prima colonna della nostra ipotetica griglia che, in fin dei conti, ha e avrà una sola cella.

Ora sappiamo che il quadrato arancione è posizionato nella cella (1,1), l'unica esistente. Ora possiamo passare a centrare il contenuto. Aggiungiamo il seguente codice:

<div></div>
<style>
body {
  [...]
  justify-items: center;
  align-items: center;
}

[...]
aldo giovanni giacomo pignolo
Justify-items vs justify-content

justify-items e justify-content sono due proprietà applicabili al parent con display:grid. Entrambe servono per centrare i contenuti lungo il main axis che di base è quello orizzontale. Però c'è una sostanziale differenza: mentre justify-content si occupa di centrare l'intera griglia, comprensiva di spazi ed elementi, rispetto al riquadro, justify-items si focalizza sugli elementi interni alle celle della griglia. Nel nostro caso usiamo justify-items perché la griglia coincide con il riquadro. In futuro farò un articolo di approfondimento dedicato per spiegare bene la differenza con esempi concreti; intanto lascio il link a questo articolo del sempreverde e provvidenziale a CSS Tricks che spiega bene il layout grid e tutte le proprietà annesse.

Colori e div

Finora abbiamo ottenuto questo risultato:

Figura 4 - Il quadrato arancione è centrato, così come lo sarà ogni div che aggiungeremo.

Siamo ancora distanti dalla forma che dobbiamo raggiungere. Innanzitutto aggiungiamo il background: #243D83; per avere lo sfondo azzurro.
Sappiamo inoltre che dovremo aggiungere un totale di 4 figure: una circonferenza, un cerchio e due rettangoli. Quindi ci servono altrettanti div. Per semplicità assegniamo a ciascuno un attributo: chiameremo a la circonferenza, b il cerchio, c e d i rettangoli. In questo modo sappiamo anche a quale div corrisponde una determinata figura e colorare le forme di conseguenza.

Attenzione: le figure b, c e d sono figure piene, quindi dobbiamo appunto riempirle di un colore e useremo proprietà background: #fff;. Ma la circonferenza a è un contorno, un bordo. Nel codice CSS quindi prestiamo attenzione a utilizzare la regola che colora quel bordo di bianco, ovvero border-color: #fff;

<div a></div>
<div b></div>
<div c></div>
<div d></div>
[...]

Piccolo reminder: ricordiamoci che il selettore per l'attributo si scrive con le parentesi quadre e il nome dell'attributo all'interno. Quindi nel CSS coloriamo le figure scrivendo i selettori così:

[...]
<style>
  [...]
  [a] {
  border-color: #fff;
  }

  [b] {
  background: #fff
  }

  [c], [d] {
  background: #243D83;
  }

</style>

Ho volutamente tenuto separati a e b perché saranno figure diverse pur con lo stesso colore. I rettangoli invece possiamo inserirli nella stessa regola perché come vedremo le dimensioni sono uguali, cambia solo la posizione.
Prima di passare alla prossima fase eliminiamo il quadrato arancione cancellando completamente la regola del div generico. Non preoccupatevi: così facendo non vedrete nulla nel riquadro in anteprima ma è tutto pronto per procedere verso il prossimo passo.

Il codice completo adesso è il seguente:

<div a></div>
<div b></div>
<div c></div>
<div d></div>
<style>
body {
  margin: 0;
  display: grid;
  justify-items: center;
  align-items: center;
  background: #243D83;
}

body * {
  grid-row: 1;
  grid-column: 1;
}

[a] {
  border-color: #fff;
}

[b] {
  background: #fff
}

[c], [d] {
  background: #243D83;
}

</style>

Prima figura: la circonferenza

Benissimo, ora possiamo focalizzare l'attenzione sulle figure. Cominciamo dalla circonferenza
Abbiamo già colorato il div di bianco con la regola background: #fff;, dobbiamo solo dare la forma alla circonferenza. Geometricamente la circonferenza non è altro che il bordo, o border per usare il CSS, di un cerchio. Costruiamo un cerchio nel modo più classico: disegnando un quadrato con width e height, dandogli un border-radius: 50%; per curvare il perimetro. Agiamo ovviamente nella regola di [a], che come abbiamo detto abbiamo assegnato alla circonferenza:

[...]
<style>
  [...]
  [a] {
  width: 200px;
  aspect-ratio: 1;
  border-radius: 50%;
  border: 20px solid #fff;
  }
  [...]
</style>

Tre note a margine sul codice appena scritto.
Innanzitutto date per buone le misure in pixel. Si tratta solo di azzeccare le dimensioni della figura, nulla di più.
In secondo luogo notiamo un altro trick che ho utilizzato per costruire il quadrato: invece di inserire le height, ho optato per un più furbo aspect-ratio: 1, una regola decisamente auto-esplicativa; impone al div di mantenere le proporzioni 1:1 tra i lati del quadrato o del cerchio. Così facendo, è sufficiente cambiare il valore della width per cambiare automaticamente anche la height. Terzo: ho eliminato la proprietà border-color: #fff sostituendola con la versione short border, per inserire in una sola proprietà anche spessore del bordo e tipologia (solid, dotted, dashed etc).

Finalmente vediamo la nostra prima figura:

Figura 5 - La circonferenza è fatta e coincide perfettamente con la figura di riferimento

Seconda figura: cerchio

Per costruire il cerchio potremmo scegliere la via più facile e replicare esattamente quando fatto per la circonferenza, ovvero width e height per definire larghezza e altezza di un quadrato, border-radius: 50% per arrotondare il tutto e un background: #fff come tocco finale (non border perché qui stiamo parlando di cerchio pieno). Ma la banalità non ci piace.
Perché non rispolverare il buon vecchio clip-path, dico. Magari non sarà la scelta ottimale ma cogliamo l'occasione per vedere anche un secondo modo di creare un cerchio in CSS.

La partenza è identica: width: 120px; (il cerchio è più piccolo della circonferenza) e aspect-ratio: 1. Però al posto del border radius usiamo clip-path: circle(50%): clip-path ritaglia il quadrato con una maschera della forma indicata che avrà un raggio pari al parametro indicato tra parentesi, nel nostro caso 50% della larghezza totale del div.

Il codice definitivo del nostro cerchio è il seguente:

[...]
<style>
  [...]
  [b] {
  width: 120px;
  aspect-ratio: 1;
  clip-path: circle(50%);
  background: #fff
  }
  [...]
</style>

Visivamente siamo sempre più vicini alla figura da replicare:

Figura 6 - Abbiamo aggiunto anche il cerchio al nostro progetto. Come dicevamo nel primo paragrafo tutte le figure appaiono sovrapposte e centrate.

Terza e quarta figura: rettangoli

Il resto del progetto è tutto in discesa. Per entrambi i rettangoli scriviamo nella regola di [c] e [d] due proprietà:width: 300px, una height: 45.5px. I rettangoli c e d sono sovrapposti, centrati e orizzontali. Ma uno dei due deve essere in verticale. Per fare questo aggiungiamo una regola con selettore solo [d] che ruoti la figura in questione di 90°. La proprietà è transform: rotate(90deg) che direi si spiega da sola. Il codice è questo:

[...]
[c], [d] {
  width: 300px;
  height: 45.5px;
  background-color: #243D83;
  z-index: 1;
}

[d] {
  transform: rotate(90deg);
}

La figura che otteniamo è questa:

Figura 7 - Dovremmo aver finito ma c'è qualcosa nella figura che non torna.

Il problema dello stacking context

C'è un problema evidente nella figura: vediamo che il rettangolo verticale si vede, si interseca perfettamente sia alla circonferenza e al cerchio, eliminando visivamente porzioni di queste ultime figure. Ma quello orizzontale, il c per intenderci, si vede solo a metà. In realtà c'è, ma è dietro tutto. Questo perché la proprietà clip-path genera un altro cosiddetto stacking context. Lo stacking context è una concettualizzazione tridimensionale degli elementi HTML in un ipotetico asse z. Immaginatelo come se gli elementi fossero dei fogli, dei layer uno sopra l'altro. Questa concettualizzazione permette a certe regole di non interferire con altre e determina inoltre l'ordine di render dei vari elementi. Per una spiegazione più approfondita vi rimando alla documentazione ufficiale di developer.mozzilla.org

Lo stacking context è un concetto da approfondire in un altro articolo. Ora ci interessa la pratica, vogliamo capire cosa sta succedendo

Se provassimo a togliere la proprietà transform: rotate(90deg), vedremo che, nonostante l'ordine dei nostri div, il cerchio risulta sopra a tutto, in primo piano. In effetti è così: aggiungere clip-path significa creare un altro stacking context che prescinde dalla posizione nel nostro codice HTML del div in oggetto e a livello di layer lo renderizza per ultimo, sovrapposto a tutti gli altri.

Perché allora vediamo il rettangolo in verticale? Perché anche la proprietà transform crea un nuovo stacking context, quindi il rettangolo d viene portato sopra il div del clip-path.

La metafora dei fogli

Per capire in modo ancora più esplicito cosa è successo prendiamo la metafora dei fogli di carta: ipotizziamo di avere i fogli a, b, c e d, che corrispondono alle nostre figure impilati in questo ordine, dal basso verso l'alto. Avremmo alla base il foglio a, in sommità il fogli d. Nel momento in cui diamo a b la proprietà clip-path al foglio b lo stiamo portando sopra tutto. Ora l'ordine dei foglio è a, c, d, b. Successivamente aggiungiamo un'altra regola, il transform, al foglio d, spostandolo sopra tutti. L'ordine è diventato a, c, b, d.

Figura 8 - Seguendo la metafora della risma di carta, questo è un normale progetto HTML.

Soluzione e conclusioni

La soluzione al problema dello stacking context è molto più semplice di quanto si possa pensare. Ci sarebbero mille modi in realtà, come ad esempio aggiungere una qualsiasi regola per creare un nuovo stacking context per mettere in primo piano il rettangolo rimasto dietro. Ma è molto più semplice inserire nel nostro codice una piccola, banale e molto intuitiva regola che si sposa bene con la spiegazione dei paragrafi precedenti: z-index: 0 alla regola con selettori [c], [d] e voilà, il gioco è fatto. Z-index infatti stabilisce l'ordine dei layer sull'asse z (la questione è più complessa, ci sono delle regole da rispettare per usare questa proprietà, ma nel nostro caso va bene così).

Il codice definitivo per replicare la figura proposta da CSS Battle è il seguente:

<div a></div>
<div b></div>
<div c></div>
<div d></div>
<style>
body {
  margin: 0; 
  background: #243D83; 
  display: grid; 
  justify-items: center;
  align-items: center; 
}

body * {
  grid-column: 1;
  grid-row: 1;
}

[a] {
  border-radius: 50%;
  border: 20px solid #fff;
  width: 200px;
  aspect-ratio: 1;
}

[b] {
  clip-path: circle(50%);
  background: #fff;
  width: 120px; 
  aspect-ratio: 1;
}

[c], [d] {
  width: 300px;
  height: 45.5px;
  background-color: #243D83;
  z-index: 0;
}

[d] {
  transform: rotate(90deg);
}

</style>

Possiamo vedere come la nostra figura finale coincida perfettamente con quella originale:

Figura 9 - Il nostro progetto e la composizione proposta coincidono perfettamente. Missione compiuta..

Questa CSS Battle ci ha permesso di ripassare un po' di fondamentali. Chiaramente ci tengo a precisare che non stiamo parlando nè di una pagina web nè di un'altra applicazione di qualsiasi tipo, quindi non necessariamente i metodi utilizzati sono gli stessi che si usano invece nella pratica del lavoro quotidiano. Però trovo estremamente utile approfondire certi concetti per capire il funzionamento del codice nei dettagli ed essere più agevole quando poi affrontiamo una problematica.

Questo articolo è efficace nel suo intento anche nel caso utilizzaste l'intelligenza artificiale, strumento onnipresente e innegabilmente utile. Dietro l'intelligenza artificiale ci deve essere un'intelligenza umana, che orchestra lo strumento a seconda delle sue necessità. Per questo l'argomento va conosciuto e dopo ben vengano tutti i tool che facilitano il lavoro.

Fatemi sapere cosa ne pensate e ovviamente se avete in mente progetti più grandi di una figura in CSS contattatemi come preferite e sarò lieto di aiutarvi a raggiungere l'obbiettivo.

Alla prossima!