Sidebar CMS accessibile in Webflow (scroll lock + focus trap)

Avanzato 49:08 webflowgsapaccessibilitàcmsfocus-trap

Costruisci una sidebar guidata dal CMS in Webflow con GSAP — contenuto per card, slide-in, scroll lock, chiusura al click esterno e un vero focus trap per la tastiera.

Punti chiave

  • Metti una sidebar dentro ogni item della collezione CMS, così ogni card ha il suo pannello legato al contenuto di quell'item — non c'è un'unica sidebar condivisa da cablare.
  • I target filter di GSAP (previous sibling of, parent of, contains il trigger) permettono a una sola interazione guidata da attributi di trovare la sidebar giusta rispetto alla card cliccata.
  • Lo scroll lock è una classe overflow-hidden attivata da una set action all'inizio dell'apertura e rimossa alla fine della chiusura (start 0.8s), così la pagina non scorre mai dietro un pannello aperto.
  • L'accessibilità è più di uno slide-in: role="dialog", aria-modal="true", aria-labelledby legato all'id del titolo (via slug CMS), veri elementi button con aria-label e aria-hidden sulle icone decorative.
  • Un vero dialog ha bisogno di un focus trap — un piccolo script che porta il focus nel pannello all'apertura, tiene il Tab in ciclo al suo interno, chiude con Escape e riporta il focus sulla card che l'ha aperto.

Capitoli del video

  1. 00:00 Intro
  2. 00:49 Costruire la struttura della sidebar
  3. 19:47 Creare l'interazione di apertura
  4. 32:45 Creare l'interazione di chiusura
  5. 43:19 Completare il setup di accessibilità
  6. 48:05 Outro

Questa lezione costruisce una sidebar che fa molto più che scivolare dal bordo. Ogni card di una griglia CMS apre la propria sidebar con dettagli extra, lo scroll della pagina si blocca mentre è aperta, puoi chiuderla cliccando fuori e — la parte che la maggior parte dei tutorial salta — è davvero accessibile: semantica di dialog corretta e un vero focus trap per chi usa tastiera e screen reader.

Le tecniche interessanti sono due: usare i target filter relazionali di GSAP così una sola interazione serve ogni card, e stratificare l’accessibilità che trasforma un bel pannello in un vero dialog.

Come funziona

Una sidebar per item CMS. Dato che la sidebar sta dentro l’item della collezione, ogni card renderizza il proprio pannello legato ai campi di quell’item — thumbnail, titolo, summary, immagine della sidebar. Clicchi una card e la sidebar corrispondente è già lì. Il wrapper della sidebar è position: fixed, a tutto viewport, z-index alto, display: none di default; al suo interno un overlay semi-trasparente (position: absolute, full) e un content wrapper spinto a destra (margin-left: auto, ~50% di larghezza, max-width, overflow: auto così il contenuto alto resta scrollabile).

Targeting guidato da attributi con i filtri. Dato che ci sono molte sidebar identiche, l’interazione non può colpire per classe — deve trovare la sidebar di questa card. Il trucco sono gli attributi custom (data-animate, data-sidebar-open, data-sidebar-close) più i target filter di GSAP, che colpiscono rispetto al trigger: previous sibling of trigger (la sidebar accanto al pulsante di apertura), within parent of trigger (overlay/contenuto dentro l’item condiviso) e contains trigger (la sidebar che avvolge il controllo di chiusura cliccato). Una interazione, target corretto ogni volta.

Apri, chiudi e scroll lock. L’interazione di apertura è una set action che porta il display della sidebar a flex (0s), una animate che fa il fade in dell’overlay (from opacity 0), una animate che fa scivolare dentro il contenuto (from move-X 100%) e una set action che aggiunge una classe overflow-hidden al body per bloccare lo scroll. L’interazione di chiusura è l’inverso: fade out dell’overlay, slide out del contenuto, poi — a start 0.8s, finita l’animazione — set del display a none e remove della classe di scroll lock. Sia il pulsante di chiusura sia l’overlay portano data-sidebar-close, così una sola interazione di chiusura gestisce click sul pulsante e click esterno.

Accessibilità vera, non decorazione. Ogni elemento che attiva un’azione è un vero button (custom element, type="button") con un aria-label, e le icone decorative ricevono aria-hidden="true". La sidebar stessa ottiene role="dialog", aria-modal="true" e aria-labelledby che punta all’id del titolo — impostato dallo slug CMS così il dialog di ogni item annuncia il proprio titolo. Infine tabindex="-1" rende il pannello focusabile via script senza aggiungerlo all’ordine di tab.

Il focus trap. Un dialog non è accessibile finché il focus non è gestito. Un piccolo script (in Before </body>) ricorda quale card ha aperto il pannello, porta il focus dentro la sidebar (sul pulsante di chiusura) all’apertura, intrappola il Tab così cicla tra i controlli del pannello invece che tra la pagina dietro, chiude con Escape e riporta il focus sulla card d’origine alla chiusura. È questo che lo fa comportare come un vero dialog modale per chi usa la tastiera.

Come si usa

  1. Struttura (dentro l’item CMS). Div sidebar: position: fixed, full, z-index ~3000, display: none. Dentro: un overlay (position: absolute, full, nero 50%) e un sidebar_content-wrapper (margin-left: auto, ~50% di larghezza, max-width, overflow: auto, position: relative). Lega immagine/titolo/summary ai campi CMS.

  2. Marca tutto. data-animate su sidebar, overlay e content wrapper (valori distinti); data-sidebar-open sul pulsante di apertura della card; data-sidebar-close su pulsante di chiusura e overlay. Rendi i controlli apri/chiudi/overlay veri button custom element con aria-label.

  3. Interazione di apertura. set display flex (target sidebar via previous sibling of trigger), animate opacity dell’overlay from 0, animate move-X del contenuto from 100%, set overflow-hidden sul body — tutto a 0s, 0.8s, ease in-out expo.

  4. Interazione di chiusura. Trigger su data-sidebar-close. Inverti le animazioni (overlay a 0, contenuto a 100%), poi a start 0.8s set display none (target via contains trigger) e remove della classe overflow-hidden.

  5. Accessibilità. Sulla sidebar: role="dialog", aria-modal="true", aria-labelledby=<slug> (e imposta l’id del titolo sullo stesso slug), tabindex="-1". Incolla lo script di focus trap in Before </body>. Pubblica e prova con la tastiera (Tab, Escape) sul link live.

Lo script di focus trap / tastiera viene dal cloneable — copialo da lì invece di ricostruirlo, e verifica che corrisponda ai tuoi nomi di attributo.

Risorse

Domande frequenti

Come do a ogni card CMS la sua sidebar in Webflow?
Metti il div della sidebar dentro l'item della collezione. Dato che fa parte del template dell'item, ogni card renderizza la propria sidebar legata ai campi di quell'item (immagine, titolo, summary), così cliccare una card apre sempre il pannello con il contenuto giusto — nessuna sidebar condivisa da gestire.
Come fa una sola interazione ad aprire la sidebar corretta per la card cliccata?
Guidala con attributi custom e i target filter di GSAP invece che con i nomi di classe. A seconda di dove sta il target rispetto al trigger, usa "previous sibling of trigger", "within parent of trigger" o "contains trigger" — così la stessa interazione risolve la sidebar giusta per la card che l'ha attivata.
Come blocco lo scroll della pagina mentre una sidebar è aperta?
Crea una classe overflow-hidden (overflow: hidden sul body) e falla attivare da una set action di GSAP: aggiungila all'inizio dell'apertura, rimuovila alla fine della chiusura (start 0.8s, pari alla durata dell'animazione) così lo scroll torna solo a pannello del tutto chiuso. Tieni la classe applicata da qualche parte nello style guide così Webflow non la elimina.
Cosa rende accessibile una sidebar in Webflow?
Sul pannello: role="dialog", aria-modal="true" e aria-labelledby che punta all'id del titolo (impostato dallo slug CMS così è unico per item). Rendi i trigger di chiusura e overlay veri elementi button con type="button" e un aria-label, e aggiungi aria-hidden="true" alle icone puramente decorative.
Come aggiungo un focus trap e il supporto tastiera a una sidebar?
Dai al pannello tabindex="-1" (focusabile via script, non nell'ordine di tab) e aggiungi un piccolo script che, all'apertura, porta il focus sul pulsante di chiusura, tiene il Tab in ciclo dentro il pannello, chiude con Escape e riporta il focus sulla card che l'ha aperto quando si chiude.

← Torna al corso