11 novembre 2026

Lascia che sia il CSS a decidere il layout: gallerie che si adattano al numero di elementi

Con :has(), :nth-child() e una custom property puoi ottenere griglie “intelligenti” senza classi modificatrici, props o condizioni in JavaScript.

Costruire una gallery che cambi numero di colonne in base a quanti item contiene è più semplice (e pulito) di quanto sembri: il CSS moderno può “contare” i figli e reagire di conseguenza. In questo articolo vediamo come combinare :has(), :nth-child(), odd/even e una CSS custom property per ottenere layout resilienti e auto-adattivi.

Quando progetti un componente “gallery”, la tentazione è sempre la stessa: aggiungere una prop, una classe modificatrice o una condizione lato JS per dire quante colonne usare.

Il problema è che così il layout smette di essere un comportamento emergente del contenuto e diventa una configurazione manuale. E ogni volta che il numero di elementi cambia (filtri, paginazione, A/B test, contenuti dinamici) ti ritrovi a inseguire edge case.

Con un po’ di CSS moderno, invece, puoi far sì che sia il componente a adattarsi automaticamente in base a quanti elementi contiene.

Obiettivo: una griglia che “capisce” quanti item ha

Immagina una regola semplice e pragmatica, ad esempio:

  • 2 o 4 item → 2 colonne (più armonioso)
  • 6 item → 3 colonne (spesso perfetto)
  • 7+ item → 4 colonne (riempi meglio lo spazio)

Niente classi tipo .gallery--two-cols, niente JS che conta i figli. Solo CSS.

Base: una grid con numero di colonne configurabile

Partiamo con una grid normale, ma invece di fissare direttamente il numero di colonne, lo facciamo passare da una custom property.

.gallery {
  --gallery-columns: 3; /* default / fallback */
  display: grid;
  grid-template-columns: repeat(var(--gallery-columns), 1fr);
  gap: 1rem;
}

Questo dettaglio è fondamentale: il layout diventa “parametrico”. Noi, poi, cambiamo --gallery-columns solo quando serve.

Far “contare” il CSS: :has() + :nth-child()

Il selettore :has() permette di selezionare un elemento in base a ciò che contiene. È esattamente quello che ci serve.

Caso 1: “ha almeno N figli”

Per esempio: “questa gallery ha 6 o più elementi”.

.gallery:has(> :nth-child(6)) {
  /* qui dentro sei sicuro che esistono almeno 6 children */
}

Questa forma è utilissima per soglie: 6+, 7+, 10+…

Caso 2: “ha esattamente N figli”

Se vuoi essere più preciso, puoi verificare che l’nth-child(N) sia anche :last-child.

.gallery:has(> :nth-child(5):last-child) {
  /* esattamente 5 elementi */
}

È potente, ma può diventare verboso se inizi a gestire molti numeri.

Pari e dispari: una scorciatoia pratica

Per layout responsive rispetto al contenuto, spesso ti basta distinguere pari/dispari.

Esempio concettuale: “se il numero di item è pari, fai X”. In CSS lo puoi esprimere cercando un figlio che sia contemporaneamente even e last-child.

.gallery:has(> :nth-child(even):last-child) {
  /* numero di figli pari */
}

.gallery:has(> :nth-child(odd):last-child) {
  /* numero di figli dispari */
}

È un pattern sorprendentemente utile per gallerie e card grid.

Mettiamo insieme una logica realistica (senza over-engineering)

Torniamo alla regola obiettivo:

  • default 3 colonne
  • 2 o 4 item → 2 colonne
  • 6 item → rimane 3 (quindi non tocchiamo)
  • 7+ item → 4 colonne

1) Default: 3 colonne

Già fatto:

.gallery {
  --gallery-columns: 3;
  display: grid;
  grid-template-columns: repeat(var(--gallery-columns), 1fr);
  gap: 1rem;
}

2) Se il numero di figli è pari → 2 colonne… ma non se sono 6+

Qui vogliamo intercettare i casi “belli” (2 e 4), evitando di degradare i 6 a 2 colonne.

.gallery:has(> :nth-child(even):last-child):not(:has(> :nth-child(6))) {
  --gallery-columns: 2;
}

Cosa sta succedendo:

  • :has(> :nth-child(even):last-child) → il totale è pari
  • :not(:has(> :nth-child(6))) → esclude 6 o più elementi

Risultato pratico: colpisce 2 e 4, non colpisce 6, 8, 10…

3) Se ha 7 o più elementi → 4 colonne

.gallery:has(> :nth-child(7)) {
  --gallery-columns: 4;
}

Nota importante: l’ordine delle regole conta. Se definisci il caso “7+” dopo, sovrascrive eventuali impostazioni precedenti.

Perché questo approccio è più “resiliente”

  1. Niente API extra del componente: il markup resta pulito.
  2. Meno stati da gestire: il componente reagisce al contenuto, non a un flag.
  3. Meno bug ai bordi: filtri, sorting e contenuti dinamici non rompono il layout.
  4. Separazione dei ruoli: il layout è responsabilità del CSS, non del rendering.

Non significa “mai JS”: significa usare JS solo quando serve davvero, e non per compensare una cosa che il CSS può già esprimere.

Considerazioni pratiche (da non ignorare)

  • :has() è una feature moderna: in molti progetti ormai è utilizzabile serenamente, ma va comunque verificata la compatibilità con i browser target.
  • Se devi supportare browser più vecchi, puoi mantenere --gallery-columns: 3 come fallback e accettare un comportamento meno sofisticato.
  • Mantieni le regole poche e leggibili: la potenza dei selettori non deve trasformarsi in un puzzle.

Sintesi

Una gallery “intelligente” non richiede per forza classi modificatrici o logiche condizionali nel codice applicativo. Con una grid parametrica via custom property e qualche selettore mirato (:has() + :nth-child()), puoi far sì che il layout segua automaticamente il contenuto.

Il risultato è un CSS più robusto: meno configurazione manuale, meno ramificazioni, più coerenza visiva. In altre parole: lascia che sia il CSS a fare il lavoro sporco del layout.