Case study: un accordion che diventa schede
Guida alla costruzione della sezione «Anche questa pagina è un progetto» a partire dal design Figma, con HTML, CSS e JavaScript.
Riferimenti Figma
La sezione è disegnata in due frame della stessa pagina:
- Desktop: Case study — Desktop
- Mobile: Case study — Mobile
Tutti i valori (colori, dimensioni dei caratteri, spaziature, spessori delle linee, dimensioni delle icone) vanno presi da Figma. Questa guida dice che cosa costruire e come ragionarci.
Come leggere i valori da Figma: in Dev Mode seleziona un elemento e guarda il pannello delle proprietà a destra. Molti valori hanno il nome di una variabile, per esempio il colore del testo secondario o quello delle linee: il nome ti aiuta a capire quale token del progetto corrisponde. Il valore esadecimale è sempre visibile accanto al nome.
File di partenza
Verifica che la tua cartella contenga i seguenti fai:
index.html,style.cssescript.js: il tuo lavoro della lezione precedente, con la hero e la navigazione;assets/fonts/: Mona Sans e Mona Sans Mono;assets/images/: le immagini della hero e del menu, più tre immagini nuove per questa sezione:primer-study.svg,primer-layout.svg,primer-check.svg— immagini placeholder fornite dal prof.
Le icone delle card non sono file: le scriverai direttamente nell’HTML come SVG. In Figma sono le icone book, versions, code e triangle-down: copiale come SVG e incollale nel codice.
Se prosegui dal tuo progetto invece che da inizio, ti basta copiare le tre immagini nuove nella tua cartella assets/images.
0. Prima di iniziare: due modifiche alla lezione 02
Prima di aggiungere la sezione, sistema due cose della pagina che hai già.
Togliere le destinazioni provvisorie
Il <div class="chapters"> con le cinque sezioni vuote serviva solo a controllare che i link del menu funzionassero. Toglilo dall’HTML e togli le regole .chapters dal CSS. Al suo posto lascia un commento che ti ricordi la regola:
<!-- Ogni sezione del case study deve avere l'id della voce di menu corrispondente:
concept, sitemap, assets, prototipi, highlights. -->
Un link href="#assets" porta all’elemento con id="assets". Quando costruirai le sezioni vere del case study, ognuna dovrà avere l’id della sua voce di menu, scritto con le stesse lettere. Fino ad allora i link del menu non portano da nessuna parte, e il menu mobile resta aperto quando scegli una voce: il codice della lezione 02 lo chiude solo se la destinazione esiste.
Una soglia comune a 1000px
La navigazione passa in linea da 63.25rem. Porta quel valore a 1000px: da questa lezione tutta la pagina cambia disposizione alla stessa larghezza, la navigazione e la nuova sezione.
1000px è una scelta di progetto, non un valore che arriva da Figma o da un dispositivo. Le soglie si scelgono guardando quando il contenuto non sta più bene nella disposizione precedente. Averne una sola rende la pagina più facile da provare e da spiegare.
Il valore compare in due punti che devono coincidere: la media query nel CSS e matchMedia nel JavaScript.
Che cosa fa window.matchMedia?
const desktopMedia = window.matchMedia('(min-width: 1000px)');
window.matchMedia riceve una condizione scritta come nelle media query del CSS e restituisce un oggetto che la controlla. Non cambia l’aspetto della pagina.
desktopMedia.matchesvaletruese la condizione è vera in quel momento,falsese non lo è. La navigazione lo legge al caricamento per decidere se usare il menu a comparsa o i link in linea.- L’evento
changearriva quando la risposta cambia, cioè quando la finestra attraversa i 1000px in un senso o nell’altro. Non arriva a ogni pixel di ridimensionamento. La navigazione lo ascolta condesktopMedia.addEventListener('change', …).
Il CSS usa @media per cambiare l’aspetto; JavaScript usa matchMedia quando deve cambiare anche il comportamento. Nella nuova sezione basterà il CSS.
Come controllo di aver fatto tutto?
- Nella Console scrivi
desktopMedia.matchesa due larghezze diverse: deve cambiare risposta a 1000px. - Allarga la finestra da 999 a 1000px: la navigazione deve passare in linea esattamente lì.
- Cerca
63.25remnel tuo CSS: non deve comparire più.
1. La sezione e la sua intestazione
Guarda la parte alta della sezione in Figma: un’etichetta con il cursore verde, un titolo e un paragrafo, centrati, su fondo bianco, con linee sottili ai lati e sopra.
- La pagina ha già un
<h1>nella hero. Di che livello è il titolo di questa sezione? - Come fa un lettore di schermo a sapere come si chiama la sezione?
- L’etichetta con il cursore esiste già nella hero. Puoi riusarla così com’è?
Quali elementi HTML usare?
Una <section> con id="questa-pagina", dopo la hero, dentro <main>. Dagli aria-labelledby con l’id del titolo: così la sezione prende il nome dal suo titolo.
Dentro, un <header> per l’introduzione. Il titolo è un <h2>, perché l’<h1> della pagina è nella hero. Il paragrafo è un <p>.
L’etichetta è la stessa della hero: <p class="eyebrow"> con l’SVG del cursore. Non serve una classe nuova.
Quali classi usare?
Lavoriamo dentro un design system: prima di scrivere una regola nuova, chiediti se esiste già una classe che fa la stessa cosa, o se la regola potrà servire anche in altre sezioni.
- Il titolo usa
heading heading-size-4: un gradino della scala dei titoli, comeheading-size-1della hero. - Il paragrafo usa
text-size-200ecolor-muted: dimensione del testo e colore secondario. - La sezione usa
theme-light(il tema chiaro, capitolo 3) eborder-top. - L’
<header>usacontainereborder-inline: stessa larghezza della pagina e linee ai lati. - Dentro l’
<header>, un<div class="case-study-intro">raccoglie etichetta, titolo e paragrafo. È l’unica classe che riguarda solo questa sezione.
Le classi che si possono usare ovunque hanno nomi generici (color-muted, border-inline); quelle di un solo componente hanno il suo nome come prefisso.
Come disporre l’introduzione?
.case-study-intro è una colonna flex centrata (flex-direction: column, align-items: center, text-align: center) con gap fra i tre elementi.
Dagli una sola max-width, sul contenitore, e centralo con margin: 0 auto. Non servono larghezze massime separate su titolo e paragrafo. La larghezza massima è in em: dipende dalla lunghezza delle righe di testo.
Il padding verticale e orizzontale usa i token di spaziatura in pixel (capitolo 2).
2. Token e utility del design system
Prima di scrivere il componente, prepara le regole globali che userà. Guarda in Figma le spaziature della sezione, le dimensioni dei titoli e delle icone.
- Una distanza fra due blocchi della pagina e una distanza fra un’icona e il suo titolo sono lo stesso tipo di misura?
- Se l’utente ingrandisce il testo del browser, quali misure dovrebbero crescere con lui?
Pixel o em?
La regola del progetto è questa:
- Spaziature dei contenitori e distanze fra blocchi (padding della sezione, spazio sotto l’immagine, spazio fra le schede): token globali in pixel in
:root, con nomi come--spacing-40. La hero usa già--spacing-64. - Misure legate al testo (dimensione dei caratteri, icone accanto al testo, spazio fra icona e titolo, spostamenti del testo):
em.
Figma ti dà i valori in pixel. Per trasformarli in em dividi per la dimensione del carattere dell’elemento: se il testo è di 16px e la distanza di 8px, scrivi 0.5em.
Quali token e utility aggiungere?
In :root:
- i token di spaziatura che trovi in Figma e che non esistono ancora (
--spacing-*); --heading-4-font-size,--heading-4-font-weight,--heading-4-line-height;--text-350-font-size,--text-350-line-height;--icon-sizee--icon-colorper le icone.
Utility, una regola ciascuna:
.heading-size-4e.text-size-350, che leggono i token tipografici. Sul titolo aggiungitext-wrap: balance: il browser distribuisce le parole su righe di lunghezza simile;.color-muted, che applica--color-muted;.border-inline,.border-block,.border-top, che disegnano le linee con--border-widthe--color-border-muted;.icon(sotto).
3. Il tema chiaro
La hero e la navigazione sono scure, la sezione è bianca. I token in :root hanno i colori pensati per il tema scuro: su fondo bianco il testo secondario e le linee non vanno bene.
- Devi riscrivere le regole che usano quei colori, come
.eyebrow? - Se domani un’altra sezione della pagina fosse chiara, che cosa vorresti poter riusare?
Le custom properties si ereditano
Una custom property si eredita come il colore del testo: il valore scritto in :root vale per tutta la pagina, quello scritto su un elemento vale per lui e per i suoi discendenti. Quindi non devi toccare .eyebrow: basta ridefinire i token sulla sezione.
.theme-light {
--color-muted: …;
--color-border-muted: …;
--color-cursor: …;
--color-focus: …;
--color-accent: …;
color: var(--color-black);
background: var(--color-white);
}
I valori li trovi in Figma. Dentro la sezione .eyebrow legge i colori chiari, fuori quelli scuri.
Perché una classe theme-light e non regole su .case-study?
Il tema è una decisione del design system, non di questa sezione. Una classe con un nome generico si può mettere su qualunque altra sezione chiara. Dentro metti solo i token che cambiano con il tema, più il colore del testo e del fondo.
Prova: cambia --color-cursor su .theme-light. Il cursore della sezione cambia, quello della hero no.
4. Le voci: una sola struttura
Guarda le tre voci in Figma, sul desktop e sul mobile. Sul desktop sono tre schede affiancate: si vedono tutti i testi e la scheda scelta cambia l’immagine sopra. Sul mobile sono un accordion: si vede solo il testo della voce scelta, e l’immagine sopra è quella della voce scelta.
- Ti servono due strutture HTML, una per il desktop e una per il mobile, o ne basta una?
- Scegliere una voce è andare da un’altra parte o fare un’azione nella pagina? Link o pulsante?
- Che cosa può stare dentro un
<button>? E il testo della voce? - Le tre voci sono un gruppo. Esiste un elemento che lo dica al browser?
Una struttura o due?
Una. Nel sito React di riferimento il desktop e il mobile usano due strutture diverse, ma cambia solo la disposizione: gli stessi titoli, gli stessi testi, la stessa voce (.breakdownTabs-item) scelta. Se scrivi una sola struttura, il CSS la dispone in due modi con le media query, e quando la finestra cambia larghezza la voce scelta e il focus restano dove sono.
Quali elementi HTML usare?
- Le voci:
<ul class="breakdownTabs-list">con tre<li class="breakdownTabs-item">. La lista comunica che le voci sono tre e appartengono allo stesso gruppo. - In ogni voce un
<button type="button" class="breakdownTabs-button">: scegliere una voce è un’azione nella pagina, non porta a un altro indirizzo.type="button"dice che il pulsante non invia un modulo. - Dopo il pulsante, un
<p class="breakdownTabs-text">con il testo della voce.
Dentro un <button> l’HTML ammette solo contenuto in linea: niente titoli, niente paragrafi. Per questo il testo sta fuori dal pulsante, e il titolo della voce è uno <span>, non un <h3>.
Che cosa c’è dentro il pulsante?
<button class="breakdownTabs-button" type="button">
<svg class="icon" …>…</svg>
<span class="breakdownTabs-title">
<span class="text-size-350">Studiare il sistema</span>
<svg class="icon breakdownTabs-toggle" …>…</svg>
</span>
</button>
- L’icona della voce (
.breakdownTabs-item), un SVG con classeicon. - Uno
<span class="breakdownTabs-title">che raccoglie il titolo e il triangolo: in Figma stanno sulla stessa riga, con il triangolo a destra. - Il titolo usa la utility
text-size-350: non è un titolo HTML, è un testo con la dimensione di quel gradino della scala. - Il triangolo è un’altra icona, con la classe aggiuntiva
breakdownTabs-toggle.
Come segno la voce scelta?
Con una classe di stato, is-selected, sul <li> della prima voce (.breakdownTabs-item). Il prefisso is- distingue le classi che JavaScript aggiunge e toglie da quelle che descrivono un elemento.
Dai a ogni voce un id (breakdownTabs-item-study, breakdownTabs-item-layout, breakdownTabs-item-check): nel CSS ti serviranno per collegare ogni voce alla sua immagine.
Dove metto le immagini?
Sopra la lista, in un <div class="breakdownTabs-visuals">: tutte e tre, ognuna con la classe breakdownTabs-visual e una classe che richiama l’id della sua voce (.breakdownTabs-item): breakdownTabs-visual-study per breakdownTabs-item-study, e così via. Il CSS ne mostrerà una alla volta.
Ogni immagine ha il suo testo alternativo, che descrive che cosa mostra. Scrivi anche width e height con le dimensioni del file: il browser conosce la proporzione prima di scaricarla.
Tutto il componente sta in <div class="breakdownTabs border-block">, con dentro <div class="breakdownTabs-content container border-inline">.
Come controllo l’HTML prima del CSS?
Apri la pagina: vedi tre immagini, tre pulsanti con l’aspetto predefinito e tre testi. Con Tab raggiungi i tre pulsanti. Il fondo è bianco anche senza tema, perché i colori scuri sono sulla hero e sulla navigazione, non su body.
5. L’accordion del mobile
Parti dal mobile. Guarda in Figma la voce aperta e quelle chiuse: le linee che separano le voci, la linea più spessa e colorata sopra la voce aperta, il triangolo capovolto, il testo visibile solo nella voce aperta.
- Le immagini stanno prima della lista. Con quale selettore puoi dire «mostra questa immagine se quella voce ha la classe
is-selected»? - Se nascondi con il CSS i testi delle voci non scelte e JavaScript non si carica, che cosa resta leggibile?
- Il triangolo è un’icona come le altre, ma ha un colore e una dimensione diversi. Come eviti che le regole delle icone si sovrascrivano?
Scegliere l’immagine con :has()
Con + e ~ puoi raggiungere solo gli elementi che vengono dopo. :has() ti permette di partire dal contenitore e chiedere che cosa contiene:
.breakdownTabs-visual {
display: none;
}
.breakdownTabs:has(#breakdownTabs-item-study.is-selected) .breakdownTabs-visual-study {
display: block;
}
.breakdownTabs viene scelto quando contiene la voce con quell’id e la classe is-selected; da lì scendi alla sua immagine. Scrivi una regola per ciascuna delle tre voci.
Come trasformo il pulsante nell’intestazione della voce?
Togli l’aspetto predefinito: border: 0, background: none, color: inherit, font: inherit, text-align: left, width: 100%, cursor: pointer. I pulsanti non ereditano il carattere e il colore dalla pagina: devi chiederlo tu.
Il pulsante è una colonna flex (flex-direction: column, align-items: flex-start) con gap fra l’icona e la riga del titolo. La riga del titolo, .breakdownTabs-title, è a sua volta un flex orizzontale largo quanto il pulsante: justify-content: space-between porta il triangolo a destra, align-items: center lo centra sul titolo anche quando il titolo cambia dimensione.
Padding, gap e distanze vengono da Figma, in em. Scrivile come custom properties su .breakdownTabs (per esempio --breakdownTabs-button-padding): sul desktop le cambierai in un punto solo.
Come disegno le linee delle voci?
Ogni voce (.breakdownTabs-item) ha un bordo inferiore e una linea superiore creata con ::before: un elemento assoluto, largo quanto la voce, alto --border-width. Serve position: relative sulla voce.
Nella voce scelta la linea cambia colore e diventa più spessa. Invece di cambiare height, puoi usare transform: scaleY(…) con transform-origin in alto: la linea cresce senza spostare il contenuto.
Il passaggio del mouse mette in evidenza la linea allo stesso modo, ma solo dentro @media (hover: hover) and (pointer: fine): su uno schermo tattile, dopo un tocco, la linea resterebbe evidenziata.
Dove si vede il focus?
Il pulsante è solo una parte della voce (.breakdownTabs-item). Togli il contorno al pulsante e disegnalo sull’intera voce:
.breakdownTabs-button:focus-visible {
outline: none;
}
.breakdownTabs-item:has(.breakdownTabs-button:focus-visible) {
outline: var(--focus-outline-width) solid var(--color-focus);
outline-offset: var(--focus-outline-offset);
}
:focus-visible si attiva con la tastiera, non dopo un clic del mouse. Usa gli stessi token del contorno di focus della lezione 02.
Come nascondo i testi senza rischi?
Nascondi i testi delle voci (.breakdownTabs-item) non scelte e mostra i triangoli solo se il componente ha la classe breakdownTabs-ready:
.breakdownTabs-ready .breakdownTabs-item:not(.is-selected) .breakdownTabs-text {
display: none;
}
.breakdownTabs-ready .breakdownTabs-toggle {
display: block;
}
Il triangolo di partenza ha display: none. La classe breakdownTabs-ready la aggiungerà JavaScript (capitolo 7). Senza JavaScript i pulsanti non cambiano voce: se i testi fossero già nascosti, nessuno potrebbe leggerli. Così, se lo script non si carica, la sezione resta leggibile per intero.
Per provare il CSS prima di scrivere il JavaScript, aggiungi a mano breakdownTabs-ready negli strumenti di sviluppo e sposta is-selected da una voce all’altra.
6. Le schede del desktop
Guarda il frame desktop: le stesse tre voci, in tre colonne uguali. Tutti i testi sono visibili, non ci sono triangoli né linee sotto le voci. Il titolo della scheda scelta è nero, gli altri sono grigi. Nel design tutta la colonna è cliccabile, anche il testo.
- Da quale larghezza passi alle schede?
- Il testo sta fuori dal pulsante. Come fai a rendere cliccabile anche lui?
- Ti serve JavaScript per passare da accordion a schede?
Come dispongo le tre colonne?
Tutto sta in @media (min-width: 1000px), la stessa soglia del menu.
.breakdownTabs-list {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
Tre colonne uguali: minmax(0, 1fr) permette alle colonne di restringersi anche con parole lunghe. Lo spazio fra le colonne è un token di spaziatura.
Dentro la media query cambia anche le custom properties del pulsante (padding, gap, distanza del testo) e le spaziature del contenitore e dell’immagine, prendendole dal frame desktop.
Come mostro tutti i testi?
Riscrivi lo stesso selettore del capitolo 5 con display: block, dentro la media query. I due selettori hanno la stessa specificità: vince quello scritto dopo, che vale solo da 1000px. Allo stesso modo nascondi il triangolo e togli il bordo inferiore alle voci (.breakdownTabs-item).
Il colore: le voci hanno color: var(--color-muted), la voce scelta color: var(--color-black). Il titolo lo eredita.
Come rendo cliccabile tutta la colonna?
Il testo non può stare dentro il pulsante, ma il pulsante può coprire tutta la voce (.breakdownTabs-item) con un suo pseudo-elemento:
.breakdownTabs-button::after {
content: "";
position: absolute;
inset: 0;
}
Un elemento assoluto con inset: 0 si estende fino ai bordi del primo antenato posizionato: .breakdownTabs-item, che ha già position: relative. Un clic sullo pseudo-elemento è un clic sul pulsante.
Perché non serve matchMedia?
Perché la struttura è una sola: cambia la disposizione, non il documento né il comportamento. Il pulsante sceglie la voce (.breakdownTabs-item) a tutte le larghezze; il CSS decide come mostrarla.
7. JavaScript: scegliere una voce
Fin qui hai spostato is-selected a mano negli strumenti di sviluppo. Ora scrivi il codice che la sposta quando si preme un pulsante. Il codice è breve: il lavoro sta nel capire che cosa deve fare, un passo alla volta. Per ogni passo ci sono due aiuti, il secondo più esplicito del primo. Apri il secondo solo se il primo non ti basta, e cerca su MDN i nomi che trovi.
- Quali elementi della pagina ti servono? Uno solo o più di uno?
- Quando premi il secondo pulsante, quale voce deve avere
is-selected? E che cosa deve succedere alle altre due? - Il pulsante sa di essere il secondo? Come glielo fai sapere?
- Devi scrivere codice apposta per la tastiera?
Passo 1: trovare gli elementi
Indizio
Ti servono tre cose: il contenitore del componente, tutte le voci (.breakdownTabs-item) e tutti i pulsanti. Conservale in tre costanti all’inizio della parte nuova di script.js, con nomi che dicano che cosa contengono. Nella lezione 02 hai già cercato elementi della pagina con un selettore CSS.
Un indizio in più
Esistono due metodi di document che ricevono un selettore CSS: uno restituisce il primo elemento che corrisponde, l’altro li restituisce tutti. Per il contenitore ti basta il primo; per voci (.breakdownTabs-item) e pulsanti ti servono tutti.
Quello che li restituisce tutti ti dà un elenco nell’ordine in cui gli elementi compaiono nell’HTML: la prima voce dell’elenco è la prima voce della pagina. Questo ti servirà al passo 3.
Cerca: document.querySelector, document.querySelectorAll, NodeList.
Passo 2: dire al CSS che JavaScript funziona
Indizio
Nel capitolo 5 hai scritto regole che valgono solo se il componente ha la classe breakdownTabs-ready. Ora è JavaScript che deve aggiungerla, appena trovato il contenitore.
Un indizio in più
Ogni elemento ha una proprietà che rappresenta l’elenco delle sue classi, con metodi per aggiungere, togliere e controllare una classe. Ti serve quello che aggiunge. Dopo averlo scritto, ricarica a 375px: i testi delle voci (.breakdownTabs-item) non scelte devono sparire e i triangoli comparire.
Cerca: Element.classList, classList.add.
Passo 3: scegliere una voce
Indizio
Scrivi una funzione che sceglie una voce (.breakdownTabs-item). Le serve un’informazione: quale voce scegliere. Passagliela come parametro, sotto forma di posizione nell’elenco delle voci.
Dentro la funzione devi passare in rassegna tutte le voci: a quella nella posizione ricevuta aggiungi is-selected, a tutte le altre la togli. Così, qualunque fosse la voce scelta prima, alla fine una sola ha la classe.
Un indizio in più
- Per passare in rassegna le voci (
.breakdownTabs-item) usa un cicloforcon un indice: parte da 0 e cresce di 1 finché è minore del numero di voci. - Il numero di elementi di un elenco è nella sua proprietà
length. - Con le parentesi quadre e l’indice raggiungi una voce precisa. Si conta da 0: la seconda voce ha indice 1.
- Dentro il ciclo, un
if/elseconfronta l’indice con la posizione ricevuta. Per confrontare usa===. - Nei due rami usi due metodi di
classList: uno aggiunge, l’altro toglie.
Non serve ricordare quale voce era scelta prima: togliendo la classe a tutte le altre, la vecchia scelta sparisce da sola.
Prima di collegare i pulsanti puoi provare la funzione dalla Console: chiamala con 2 e guarda se si apre la terza voce.
Cerca: for (MDN, JavaScript), NodeList.length, «strict equality», classList.remove.
Passo 4: collegare i pulsanti
Indizio
Ogni pulsante, quando viene premuto, deve chiamare la funzione del passo 3 passandole la propria posizione. Il primo pulsante passa 0, il secondo 1, il terzo 2.
Visto che anche i pulsanti sono in un elenco ordinato come le voci (.breakdownTabs-item), la posizione di un pulsante nel suo elenco è la stessa della sua voce.
Un indizio in più
- Un secondo ciclo
forpassa in rassegna i pulsanti. - A ogni giro registri sul pulsante un gestore dell’evento
click, come hai fatto nella lezione 02 per il pannello del menu e per il link che salta al contenuto. - Dentro il gestore chiami la funzione del passo 3 con l’indice del ciclo.
- Dichiara l’indice del ciclo con
let, non convar: conletogni giro ha la sua variabile, e ogni gestore ricorda l’indice del proprio giro. Se hai dubbi, prova convare osserva quale voce (.breakdownTabs-item) si apre premendo i pulsanti.
Cerca: EventTarget.addEventListener, evento click, «let nei cicli for».
Passo 5: la tastiera
Indizio
Prova prima di scrivere altro: raggiungi un pulsante con Tab e premi Invio, poi Spazio. Che cosa succede?
Un indizio in più
Non serve altro codice. Un <button> genera l’evento click anche quando lo attivi da tastiera con Invio o Spazio: il gestore del passo 4 funziona già. È uno dei motivi per cui abbiamo usato un pulsante vero e non un elemento qualsiasi con un gestore di clic.
Cerca: «button activation behavior», elemento <button> su MDN, sezione sull’accessibilità.
Come controllo il JavaScript?
- A 375px i testi delle voci non scelte sono nascosti e compaiono i triangoli.
- Tocca la terza voce, poi usa Tab e Invio.
- Allarga fino a 1440px senza ricaricare: la terza voce resta scelta e il focus resta sul suo pulsante.
- Disattiva JavaScript negli strumenti di sviluppo e ricarica: tutti i testi tornano visibili, i triangoli spariscono.
- Apri la Console: non ci devono essere errori.
8. Schermi stretti
Confronta il frame mobile con il desktop: il titolo della sezione, i titoli delle voci e gli spazi attorno al componente sono più piccoli.
- A quale larghezza la hero cambia già dimensioni?
- Devi riscrivere le regole dei titoli, o ti basta cambiare qualcos’altro?
Come cambio caratteri e spazi?
Usa la stessa soglia della hero, @media (max-width: 640px), e soltanto quella: fra 641 e 999px la sezione è un accordion con i caratteri e gli spazi del desktop.
Dentro la media query cambia i token, non le regole:
- in
:root,--heading-4-font-sizee--text-350-font-size; - i padding di
.case-study-introe di.breakdownTabs-content, con i token di spaziatura.
Le misure in em delle voci (.breakdownTabs-item) si adattano da sole alla nuova dimensione dei caratteri.
La sezione non ha animazioni: i cambi di voce, di linea, di colore e di triangolo sono immediati.
Accessibilità: il prossimo passo
Il componente funziona con mouse, tocco e tastiera, e il testo di ogni voce viene letto subito dopo il suo pulsante. Però i pulsanti non dicono il proprio stato: un lettore di schermo legge «Comporre la pagina, pulsante», senza dire se la voce è scelta o aperta, e il cambio d’immagine sopra la lista non viene annunciato.
Non devi implementarlo adesso, ma è importante sapere che cosa manca e perché non lo aggiungiamo a metà. Un ruolo ARIA promette un comportamento preciso: se il comportamento manca, chi usa un lettore di schermo riceve istruzioni sbagliate. Meglio nessun attributo ARIA che uno scritto male.
Che cosa servirebbe sul desktop?
Sul desktop le voci (.breakdownTabs-item) si comportano come schede (tab):
role="tablist"sul contenitore dei pulsanti;role="tab"su ogni pulsante, conaria-selected="true"o"false"earia-controlsche indica il pannello;role="tabpanel"sull’area che cambia, conaria-labelledbyche indica la scheda;- la tastiera prevista per le schede: Tab entra nel gruppo una volta sola, le frecce spostano scelta e focus, Home e Fine vanno alla prima e all’ultima. Serve JavaScript per
tabindexe per i tasti.
Un tablist può contenere solo tab: i testi delle schede andrebbero spostati fuori dalla lista o collegati ai pulsanti in un altro modo.
E sul mobile?
Sul mobile le voci (.breakdownTabs-item) si comportano come un accordion. Le strade sono due:
- duplicare il contenuto: le schede per il desktop e una serie di
<details>con lo stessonameper il mobile, mostrando una struttura alla volta con le media query; - tenere una sola struttura: sul mobile JavaScript toglie i ruoli delle schede e dà ai pulsanti
aria-expandedearia-controls, che li collegano al proprio testo; sul desktop rimette i ruoli.
In entrambi i casi il cambio di larghezza va seguito con matchMedia e il suo evento change, e il focus non deve andare perso quando una struttura viene nascosta.
Ordine consigliato di sviluppo
- Le due modifiche della lezione 02: togli le destinazioni provvisorie, porta la soglia a 1000px in CSS e JavaScript, controlla il menu.
- Tutto l’HTML della sezione: intestazione, immagini, lista, pulsanti, testi. Apri la pagina: deve essere brutta ma completa, e ogni pulsante raggiungibile con Tab.
- Token e utility in
:root, poi il tema chiaro. Controlla che la hero non cambi. - Il mobile: immagini con
:has(), pulsanti, linee, triangolo, focus. Prova gli stati aggiungendo a manobreakdownTabs-readye spostandois-selected. - Il desktop dentro
@media (min-width: 1000px). - Il JavaScript, e le prove con mouse, tastiera e JavaScript disattivato.
- Gli schermi stretti sotto 640px, per ultimi.
Dopo ogni passo confronta la pagina con Figma alle due larghezze dei frame.
Riepilogo delle tecniche utilizzate
In questa sezione hai usato:
- Una sola struttura HTML per due disposizioni: lista, pulsanti e testi restano gli stessi; il CSS li dispone come accordion o come schede.
- Una classe di stato,
is-selected, letta da tutte le regole che dipendono dalla voce scelta. :has()per scegliere un elemento in base a ciò che contiene un suo antenato.- Una classe aggiunta da JavaScript,
breakdownTabs-ready, per nascondere contenuti solo quando i controlli funzionano. - Il tema come classe che ridefinisce le custom properties, sfruttando l’ereditarietà.
- Token globali in pixel per le spaziature dei contenitori e
emper le misure legate al testo. - Utility del design system (
heading-size-4,text-size-350,color-muted,border-*,icon) con nomi generici, e il prefisso del componente per il resto. - Custom properties ereditate per evitare conflitti di specificità.
::beforeper le linee e::afterconinset: 0per allargare l’area cliccabile di un pulsante.- Contorno di focus su un antenato con
:has(:focus-visible). - Grid con
repeat(3, minmax(0, 1fr))per colonne uguali. - Media query
hoverepointerper gli effetti al passaggio del mouse. - JavaScript di base:
querySelectorAll, cicloforcon indice,if/else,classList,addEventListener. - Che cosa manca per l’accessibilità, e perché è meglio nessun ARIA che un ARIA incompleto.