DEV Community

frontendfacile.it
frontendfacile.it

Posted on Originally published at frontendfacile.it

Carousel in puro CSS: scroll-snap, pulsanti e marker nativi (senza JavaScript)

Le nuove API di scroll per costruire caroselli accessibili, con controlli e indicatori gestiti dal browser.

I carousel dividono: c’è chi li associa subito ai grandi “hero slider” pieni di autoplay e contenuti ignorati, e chi invece li usa ogni giorno per pattern molto più utili (liste prodotto, gallerie, app store, playlist, card di articoli, recensioni). Il punto è semplice: su schermi piccoli e layout densi, comprimere più elementi in una fascia orizzontale scorrevole spesso è la scelta più pragmatica.

La cosa interessante è che un carousel non è magia: è uno scroll container con qualche regola in più. E negli ultimi rilasci di Chrome sono arrivate API CSS che alzano notevolmente il livello: controlli nativi, indicatori, stato “corrente” e perfino possibilità di rendere inert ciò che è off-screen.

Di seguito una panoramica ragionata (e pratica) di come comporre un carousel moderno in CSS.


1) La base: un carousel è uno scroller con scroll-snap

La struttura minima parte da un contenitore che scorre orizzontalmente e da item che “agganciano” una posizione.

.carousel {
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}

.carousel > * {
  scroll-snap-align: center;
}
Enter fullscreen mode Exit fullscreen mode

Risultato: puoi scorrere liberamente e, al termine del gesto, l’elemento più vicino si allinea al centro.

Nascondere la scrollbar senza uccidere lo scroll

Per un look più “carousel”, spesso conviene nascondere la barra mantenendo comunque lo scorrimento attivo:

.carousel {
  scrollbar-width: none;
}
Enter fullscreen mode Exit fullscreen mode

Questa scelta è molto diversa da overflow: hidden: lo scroll rimane disponibile (touch/trackpad/ruota), semplicemente non “consuma” spazio con la scrollbar.

Il problema successivo però è inevitabile: se non vedo la scrollbar, come capisco che posso scorrere? Qui entrano in gioco i controlli nativi.


2) Pulsanti di scorrimento nativi con ::scroll-button()

CSS introduce un pseudo-elemento funzionale per generare pulsanti “precedente/successivo” direttamente nello scroll container:

  • ::scroll-button(left) / ::scroll-button(right) per caroselli orizzontali
  • ::scroll-button(up) / ::scroll-button(down) per quelli verticali
  • alternative logiche consigliate: inline-start, inline-end, block-start, block-end

I pulsanti vengono creati solo quando assegni un content diverso da none.

Esempio (con etichette accessibili incluse via content):

.carousel {
  /* ...base scroll... */

  &::scroll-button(left) {
    content: "←" / "Scorri a sinistra";
  }

  &::scroll-button(right) {
    content: "→" / "Scorri a destra";
  }

  &::scroll-button(*) {
    aspect-ratio: 1;
    border-radius: 999px;
    background: color-mix(in oklab, Canvas, CanvasText 10%);
    border: 1px solid color-mix(in oklab, CanvasText, transparent 70%);
  }

  &::scroll-button(*):focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}
Enter fullscreen mode Exit fullscreen mode

Comportamento “gratis”: disabled automatico

Un vantaggio enorme è che il browser gestisce lo stato: quando non puoi più scorrere in una direzione, il relativo pulsante risulta disabilitato. Puoi anche stilizzarlo con :disabled (ad esempio per nasconderlo o ridurne l’opacità).

Posizionamento

Per metterli “sopra” al carosello o ai lati, la strada più pulita oggi è usare l’anchor positioning: consente di ancorare i pulsanti al container senza hack di layout. (Vale la pena adottare proprietà logiche anche qui, così il carousel resta pronto per RTL e localizzazioni.)

Nota: essendo pseudo-elementi, i ::scroll-button() hanno alcune limitazioni di customizzazione rispetto a elementi DOM reali—ma in molti casi sono un compromesso eccellente.


3) Indicatori (i “pallini”) con scroll-marker-group e ::scroll-marker

Il pattern classico dei puntini cliccabili può essere costruito con:

1) una proprietà sul container che abilita il gruppo marker
2) uno pseudo-elemento marker sugli item

Abilitare il gruppo

.carousel {
  scroll-marker-group: after;
}
Enter fullscreen mode Exit fullscreen mode

after o before decidono se il gruppo marker viene inserito dopo o prima nel tab order (e, in pratica, dove viene collocato nel flusso dei controlli).

Definire i marker sugli item

.carousel > li::scroll-marker {
  content: "" / "Vai all’elemento";
}
Enter fullscreen mode Exit fullscreen mode

Come per i pulsanti: niente content, niente marker.

Evidenziare quello attivo con :target-current

Per dare feedback visivo serve distinguere il marker “corrente”:

.carousel > li::scroll-marker:target-current {
  transform: scale(1.2);
  background: CanvasText;
}
Enter fullscreen mode Exit fullscreen mode

:target-current seleziona il marker attivo (quello associato all’item attualmente “in vista”/agganciato).

Marker più parlanti con i CSS counter

Dove supportato, puoi anche generare etichette più significative usando i counter CSS nel content, ad esempio “Vai all’elemento 5” invece del generico “Vai all’elemento”. È un dettaglio che migliora parecchio l’esperienza con screen reader quando gli item sono numerosi.


4) Quando vuoi controllo totale: scroll-target-group

I marker generati via pseudo-elementi sono pratici, ma proprio perché “pseudo” possono diventare stretti se vuoi markup ricco o styling molto personalizzato.

Per questo esiste scroll-target-group: invece di far generare i marker al browser, usi direttamente i link nel DOM come indicatori/target di scroll, mantenendo lo stesso comportamento.

.toc {
  scroll-target-group: auto;
}

.toc a:target-current {
  font-weight: 600;
  text-decoration: underline;
}
Enter fullscreen mode Exit fullscreen mode

Bonus: scroll-spy in due righe

Con una lista di anchor che puntano a sezioni della pagina (TOC) e :target-current, ottieni un effetto “scroll spy” senza logica JavaScript: lo stato attivo si aggiorna e puoi evidenziare la voce corrispondente.

Differenza importante rispetto a scroll-marker-group: il gruppo creato dal browser con scroll-marker-group porta con sé semantiche di navigazione attese (simili a una tablist), mentre con scroll-target-group parti da link reali e hai pieno controllo, ma anche più responsabilità sull’accessibilità e sulla UI.


5) Un approccio moderno: composizione con altre feature di scroll

Il salto di qualità arriva quando queste primitive si combinano con:

  • scroll-driven animations (animazioni guidate dallo scorrimento)
  • scroll state queries (stati legati alla posizione di scroll)
  • gestione dell’off-screen (ad esempio rendere inert ciò che è fuori viewport, dove appropriato)

Non è “solo un carousel”: diventa un componente reattivo allo scroll, senza dover reinventare continuamente il controllo dello stato in JavaScript.


Sintesi: cosa cambia davvero per chi costruisce UI

  • La base resta semplice: overflow-x: auto + scroll-snap-*.
  • I controlli diventano nativi: ::scroll-button() riduce drasticamente la necessità di JS per prev/next e relativi stati.
  • Gli indicatori si possono ottenere rapidamente con scroll-marker-group + ::scroll-marker, e stilizzare con :target-current.
  • Se serve design totalmente custom (o markup ricco), scroll-target-group ti porta su anchor reali mantenendo il comportamento di “marker”.

Implicazione pratica: vale la pena iniziare a progettare i carousel come scroll component composabili, lasciando al browser la gestione degli edge case (disabled, target corrente, navigazione) e usando JavaScript solo dove aggiunge reale valore—non per ricostruire fondamenta che ormai CSS sta rendendo standard.


Articolo originale: https://frontendfacile.it/blog/carousel-in-puro-css-scroll-snap-pulsanti-e-marker-nativi-senza-javascript

Top comments (0)