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, gall

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. 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; } Risultato: puoi scorrere liberamente e, al termine del gesto, l’elemento più vicino si allinea al centro. Per un look più “carousel”, spesso conviene nascondere la barra mantenendo comunque lo scorrimento attivo: .carousel { scrollbar-width: none; } 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. ::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; } } 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à). 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. 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 .carousel { scroll-marker-group: after; } 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). .carousel > li::scroll-marker { content: "" / "Vai all’elemento"; } Come per i pulsanti: niente content, niente marker. :target-current Per dare feedback visivo serve distinguere il marker “corrente”: .carousel > li::scroll-marker:target-current { transform: scale(1.2); background: CanvasText; } :target-current seleziona il marker attivo (quello associato all’item attualmente “in vista”/agganciato). 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. 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; } 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. 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. 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
Key Takeaways
- •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, gall
- •This story was reported by Dev.to, covering developments in the dev space.
- •AI advancements continue to reshape industries — read the full article on Dev.to for complete coverage.
📖 Continue reading the full article:
Read Full Article on Dev.to →


