Esercitazione Flashcard
Guida alla costruzione dell'interfaccia a partire dal design Figma, usando HTML semantico e CSS.
Riferimenti Figma
Usa i link qui sotto per ispezionare il design (colori, spaziature, dimensioni font, bordi, ecc.):
- Desktop: Study Mode — Desktop
- Tablet: Study Mode — Tablet
- Mobile: Study Mode — Mobile
Tutti i valori esatti (colori, padding, font-size, border-width, ecc.) vanno presi direttamente da Figma. Questa guida indica cosa costruire e come affrontarlo, non i valori pixel per pixel.
Come leggere i valori da Figma: seleziona un elemento nel pannello Dev Mode e guarda il pannello delle proprietà a destra. Troverai colori (in hex), dimensioni, padding, margini, font-family, font-weight, font-size, line-height, border-radius, box-shadow e altro. Quando il design usa token come colors/neutral/900, il valore hex corrispondente è sempre visibile — usa quello.
File di partenza
Parti con:
index.html— con DOCTYPE,<head>(incluso il link al favicon) e<body>vuotoassets/fonts/Poppins/— file.ttfper Regular (400), Medium (500), SemiBold (600), Bold (700)assets/images/— contiene:logo-large.svg— il logo dell'appicon-shuffle.svg,icon-chevron-left.svg,icon-chevron-right.svg,icon-chevron-down.svg— icone di navigazioneicon-check.svg,icon-circle-check.svg,icon-reset.svg— icone di azioneicon-stats-total.svg,icon-stats-mastered.svg,icon-stats-in-progress.svg,icon-stats-not-started.svg— icone statistichepattern-flashcard-bg.svg,pattern-star-blue.svg,pattern-star-yellow.svg— pattern decorativifavicon-32x32.png— favicon
Dovrai creare tu il file style.css.
0. Setup iniziale
Collegare il foglio di stile
Nel <head>, aggiungi un <link> al tuo style.css. Niente di nuovo qui.
Assicurati che l'HTML abbia anche <meta charset="UTF-8"> e il meta viewport <meta name="viewport" content="width=device-width, initial-scale=1.0">. Il viewport meta è essenziale: senza di esso, i browser mobile rendereranno la pagina come se fosse un desktop (tipicamente a 980px) e poi la zoomeranno fuori. Il risultato: tutto minuscolo e illeggibile.
Organizzare il CSS con @layer
Apriamo il nostro style.css: è vuoto. Cosa ci mettiamo come prima cosa? La primissima riga dichiara la struttura dell'intero foglio di stile con @layer:
@layer reset, base, layout, components, states;
Questa dichiarazione dice al browser: "le regole nel layer reset hanno la priorità più bassa, quelle in states la più alta." Non importa dove fisicamente si trovano le regole nel file — è il layer a determinare quale vince in caso di conflitto.
I cinque layer che useremo:
| Layer | Contenuto |
|---|---|
reset |
Reset globale: box-sizing, margini zero, default per img |
base |
Stili fondamentali: body, .container, .divider |
layout |
Contenitori strutturali che organizzano lo spazio: header, main, wrapper dei filtri, navigazione |
components |
Componenti con identità visiva: bottoni, tab, card, input, select, flashcard, statistiche |
states |
Stati interattivi: :hover, :focus-visible, :checked |
Perché questa suddivisione? Il vantaggio è che gli stati interattivi (layer states) vincono sempre sui componenti (layer components), indipendentemente dalla specificità dei selettori. Senza layer, se .btn:hover e .btn--primary:hover avessero la stessa specificità, conterebbe l'ordine nel file — fragile e facile da rompere riordinando il codice. Con i layer, entrambi sono in states e la specificità normale dei selettori li distingue correttamente.
Ogni sezione di regole va avvolta nel suo layer:
@layer reset {
/* regole di reset qui */
}
@layer base {
/* body, container, divider qui */
}
@layer layout {
/* contenitori strutturali qui */
}
E così via per components e states.
Un dettaglio importante: puoi aprire lo stesso @layer più volte nel file. Il browser raccoglie tutte le regole e le unisce nello stesso layer. Questo significa che non sei obbligato a mettere tutti gli stati in un unico blocco @layer states { } in fondo al file. Puoi, per esempio, tenere gli stati di ogni componente vicini al componente stesso:
@layer components {
.btn { /* stile base */ }
.btn--primary { /* variante */ }
}
@layer states {
.btn:hover { /* hover */ }
.btn--primary:hover { /* hover variante */ }
}
@layer components {
.checkbox { /* stile base */ }
}
@layer states {
.checkbox:hover { /* hover */ }
.checkbox:checked { /* checked */ }
}
La priorità tra layer resta quella dichiarata nella prima riga (reset, base, layout, components, states) — non cambia in base a quante volte apri un layer o dove lo apri nel file. Questa flessibilità ti permette di organizzare il codice per componente (ogni componente ha il suo blocco components + states vicini) oppure per layer (tutti i componenti insieme, poi tutti gli stati insieme). Entrambi gli approcci sono validi. In questa esercitazione useremo il secondo: ogni layer appare una sola volta nel file, con tutti i componenti raggruppati e tutti gli stati raggruppati alla fine. Questo rende più semplice ragionare sulla cascade quando si sta ancora costruendo l'interfaccia — sai sempre dove cercare: aspetto visivo in components, comportamento interattivo in states.
Come decidere se una regola va in layout o components? La distinzione è: se l'elemento ha solo proprietà strutturali (display: flex, gap, flex-wrap, width, padding) va in layout. Se ha identità visiva (background, border, border-radius, box-shadow, font-size, color) va in components. Alcuni elementi hanno entrambe le cose — in quel caso, mettili in components.
@font-face
Subito dopo la dichiarazione @layer, e fuori da qualsiasi layer, registriamo i font. Le @font-face sono registrazioni, non regole di stile in competizione — non hanno bisogno di partecipare alla cascade dei layer.
Per usare un font personalizzato nel browser, devi "registrarlo" tramite la regola @font-face. È una dichiarazione che dice al browser: "quando nel CSS trovi questo nome di font-family con questo peso, carica questo file."
Il design usa Poppins in quattro pesi. Ecco le dichiarazioni complete:
@font-face {
font-family: "Poppins";
src: url("./assets/fonts/Poppins/Poppins-Regular.ttf") format("truetype");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "Poppins";
src: url("./assets/fonts/Poppins/Poppins-Medium.ttf") format("truetype");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: "Poppins";
src: url("./assets/fonts/Poppins/Poppins-SemiBold.ttf") format("truetype");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "Poppins";
src: url("./assets/fonts/Poppins/Poppins-Bold.ttf") format("truetype");
font-weight: 700;
font-style: normal;
}
Scomponiamo ogni proprietà:
-
font-family: "Poppins"— il nome con cui userai questo font nel resto del CSS (es.font-family: "Poppins", sans-serif). Nota che è lo stesso identico nome per tutte e quattro le dichiarazioni — non creare nomi separati come "Poppins-Bold" o "Poppins-Medium". Il browser usa ilfont-weightper scegliere il file giusto. -
src: url(...) format(...)— il percorso al file del font e il suo formato. I file nella cartellaassets/fonts/Poppins/sono in formato TrueType (.ttf), quindi il format è"truetype". Altri formati comuni sono"woff2"(più compresso, preferibile in produzione) e"woff". -
font-weight— il peso numerico associato a questo file: 400 = Regular, 500 = Medium, 600 = SemiBold, 700 = Bold. Quando nel CSS scrivifont-weight: 700, il browser sa che deve usare il filePoppins-Bold.ttf. -
font-style: normal— distingue tra variante normale e italic. Siccome non abbiamo file italic, tutte le dichiarazioni usanonormal. Se avessimo anchePoppins-Italic.ttf, aggiungeremmo una quinta@font-faceconfont-style: italic.
Un errore molto comune è registrare un solo peso (es. Regular) e poi usare font-weight: 700 nel CSS aspettandosi il grassetto. Senza la @font-face per il peso 700, il browser tenta di "simulare" il grassetto sinteticamente — il risultato è visivamente diverso e meno pulito rispetto al vero file Bold disegnato dal type designer. Registra sempre tutti i pesi che intendi usare.
L'ordine delle dichiarazioni @font-face nel CSS non conta ai fini del funzionamento. Ma per leggibilità, conviene ordinarle per peso crescente (400, 500, 600, 700) — è una convenzione che rende il codice più facile da navigare.
Reset di base
Tre cose fondamentali:
box-sizing: border-boxsu tutti gli elementi (inclusi::beforee::after) — altrimenti padding e bordi sballano tutte le misure. Usa il selettore*, *::before, *::afterper coprire tutto- Azzerare
marginepaddingdi default del browser — ogni browser ha i suoi stili di default, e partire da zero ti dà controllo totale - Impostare
font-family,colordi testo base ebackground-colorsulbody(prendi i valori da Figma)
Un suggerimento in più: imposta display: block e max-width: 100% su un selettore img. Il primo previene il comportamento inline di default (che aggiunge spazio sotto le immagini), il secondo impedisce che le immagini escano dal loro contenitore. È un pattern che in produzione si applica praticamente sempre.
Il pattern .container
Questa è una tecnica fondamentale che userai in qualsiasi progetto web. L'obiettivo è creare una classe riutilizzabile che gestisce il contenimento orizzontale del contenuto.
Il problema che risolve: senza vincoli, il contenuto si espande a tutta la larghezza del viewport. Su un monitor da 2560px, una riga di testo lunga 2000px è illeggibile. Serve un limite.
La classe .container fa tre cose:
- Limita la larghezza massima del contenuto con
max-width— il contenuto non supera mai questa misura - Centra orizzontalmente con
margin-left: autoemargin-right: auto— lo spazio in eccesso viene distribuito equamente ai lati - Aggiunge padding laterale con
padding-leftepadding-right— su viewport più strette delmax-width, il contenuto non tocca i bordi dello schermo
Come calcolare il max-width: guarda il frame Desktop su Figma. Nota la larghezza totale e il padding orizzontale. Il max-width del container dovrebbe essere la larghezza totale del frame meno il padding sui due lati — oppure, se preferisci, puoi impostare il max-width come la larghezza totale e poi aggiungere il padding. Entrambi gli approcci funzionano, ma il secondo è più semplice da ragionare perché max-width include il padding (grazie a box-sizing: border-box).
Importante: width: 100% va specificato insieme a max-width, perché max-width da solo non imposta una larghezza base — definisce solo il limite superiore.
Un errore che capita spesso: usare margin: 0 auto e chiedersi perché non centra nulla. Il motivo è che l'elemento ha width: auto (default) — occupa già il 100% del genitore, quindi non c'è spazio da distribuire ai margini. Il max-width crea il vincolo, il width: 100% fa sì che l'elemento provi ad essere largo quanto il genitore, e quando il genitore è più largo del max-width, scatta il centraggio.
Applicherai .container sia all'<header> che al <main>. In questo modo, entrambi condividono lo stesso allineamento e la stessa larghezza massima — è fondamentale per la coerenza visiva della pagina.
La struttura semantica della pagina
Prima di passare al CSS, fermiamoci un momento sulla struttura HTML del <body>. I due figli diretti del body sono:
-
<header>— l'intestazione della pagina: contiene il logo e la navigazione principale. Rappresenta il contenuto introduttivo dell'intera pagina, non di una singola sezione. Gli screen reader lo espongono come landmark "banner", permettendo agli utenti di saltarci direttamente. -
<main>— il contenuto dominante della pagina. In un documento dovrebbe essercene uno solo. Tutto ciò che è unico a questa pagina va dentro<main>— i filtri, la flashcard, le statistiche. Contenuti che si ripetono su ogni pagina (logo, navigazione, footer) restano fuori. Gli screen reader lo espongono come landmark "main", ed è spesso il primo punto a cui un utente di tastiera salta con le scorciatoie di navigazione.
Questa distinzione non è solo teorica: un utente di screen reader che visita la pagina può premere un tasto per saltare direttamente al contenuto principale (<main>), bypassando header e navigazione. Se tutto fosse in <div> anonimi, questa possibilità non esisterebbe.
Spaziatura verticale del body
Osserva in Figma come sono distanziati verticalmente header e contenuto principale. Un approccio è usare display: flex, flex-direction: column e gap sul body, con padding-top e padding-bottom per il respiro verticale.
Perché flex e non semplicemente margin sui figli? Perché gap è più dichiarativo — dici "lo spazio tra gli elementi è X" una volta sola, invece di mettere margin-top o margin-bottom su ciascun figlio. Inoltre, gap non crea spazio extra prima del primo elemento o dopo l'ultimo — un problema classico del margin approach che richiede workaround come :first-child / :last-child.
1. Header
Osserva l'header nel design Figma. Contiene due gruppi: il logo a sinistra e la navigazione a tab a destra. Prova a pensare a queste cose:
- Quale elemento semantico rappresenta l'intestazione di una pagina? E quale elemento segnala un blocco di navigazione agli screen reader?
- I tab ("Study Mode", "All Cards") portano a viste diverse. Sono link (
<a>) o bottoni (<button>)? Qual è la differenza concettuale? - Il tab attivo ha un bordo visibile, quello inattivo no. Se aggiungi
bordersolo al tab attivo, cosa succede al box model quando cambi tab?
Scelta degli elementi HTML
L'elemento per l'intestazione della pagina è <header>. Dagli le classi header e container.
Per i tab, usa un <nav> con classe .tab-nav — l'elemento <nav> segnala un blocco di navigazione. Gli screen reader lo espongono come landmark "navigation", permettendo agli utenti di raggiungerlo con una scorciatoia. Non ogni gruppo di link è un <nav> — solo quelli che rappresentano la navigazione principale.
Dentro il <nav>, i tab sono <a> con classe .tab-button. Perché <a> e non <button>? Un <a> rappresenta un collegamento a una risorsa (un'altra pagina o vista), un <button> rappresenta un'azione in-page. Se i tab cambiano vista, sono concettualmente link. Se togglassero la visibilità di un pannello nella stessa pagina senza cambiare URL, un <button> sarebbe più appropriato.
Il tab attivo ha una classe aggiuntiva .tab-button--active (naming BEM: base .tab-button, modificatore --active).
Il logo è un'<img> con logo-large.svg. L'alt dovrebbe essere il nome dell'app, dato che il logo lo contiene.
Come evitare il "salto" del box model tra tab attivo e inattivo?
Se metti border: 1px solid ... solo sul tab attivo, i due tab avranno un box model diverso — il tab attivo sarà 2px più largo e più alto (1px per lato). Quando cambi tab, tutto "salta". È un dettaglio piccolo ma visibile.
Il trick: dai border: 1px solid transparent al .tab-button base. Il bordo occupa spazio identico in entrambi gli stati. Sul .tab-button--active cambi solo border-color e aggiungi background-color. Il box model resta identico.
Come stilizzare il contenitore dei tab e i singoli tab?
Il .tab-nav è un contenitore pill-shaped: sfondo bianco, bordo, border-radius: 999px, piccolo padding interno e un sottile box-shadow solido (niente blur, piccolo offset — un pattern ricorrente in tutta l'app). Layout: display: flex con piccolo gap e align-items: center.
Ogni .tab-button è un flex container centrato con altezza specifica, padding, border-radius pill, e testo in Poppins SemiBold. Controlla Figma per i valori.
Ricordati che i <a> hanno stili di default: sottolineatura (text-decoration) e colore blu. Dovrai reimpostarli. Aggiungi white-space: nowrap per evitare che il testo vada a capo internamente.
L'header stesso è un flex container con justify-content: space-between e align-items: center. Aggiungi flex-wrap: wrap con gap — su schermi stretti, logo e tab possono andare a capo senza traboccare.
2. Sezione Flashcard — struttura
Guarda la sezione flashcard nel design Figma. È la colonna principale della pagina — una card bianca che contiene tre zone distinte (filtri, flashcard vera e propria, navigazione), separate da linee orizzontali. Pensa a:
- Questa sezione raggruppa contenuti con un tema comune ("l'area di studio"). Un
<div>basterebbe? O c'è un elemento che comunica "questo è un raggruppamento tematico"? - Le linee orizzontali tra le zone sono decorative o hanno un significato semantico? Esiste un elemento HTML per le separazioni tematiche?
- Il bordo della card è asimmetrico — più spesso a destra e in basso. Come lo faresti con una sola dichiarazione
border-width?
Quale elemento HTML per un raggruppamento tematico?
Usa una <section> con classe .flashcard-section. Perché non un <div>? Una <section> rappresenta un raggruppamento tematico di contenuto, tipicamente con un proprio titolo. In questo caso, filtri, flashcard e navigazione appartengono allo stesso contesto funzionale — "l'area di studio". Un <div> sarebbe semanticamente neutro. La regola pratica: se il contenuto ha un tema coerente e potresti dargli un titolo (anche se non lo mostri visivamente), è una <section>.
Dentro ci sono tre zone, separate da divisori <hr>:
- Filtri (form dei controlli) — sezione 3
- Area flashcard (la card + azioni) — sezioni 4 e 5
- Navigazione (Previous / Card counter / Next) — sezione 6
L'<hr> è semanticamente corretto qui: rappresenta una separazione tematica tra sezioni di contenuto. Dagli una classe .divider.
L'area flashcard (punto 2) è wrappata in un <div> con classe .flashcard-container — non è una sezione semantica separata, è un semplice contenitore per padding e layout.
Come stilizzare la sezione e i divisori?
La sezione è una card bianca con bordi asimmetrici — il bordo destro e inferiore sono più spessi. Per i bordi asimmetrici, usa border-style: solid, border-color e border-width: <top> <right> <bottom> <left> (ordine in senso orario). Controlla su Figma quali lati sono più spessi.
Layout interno: display: flex con flex-direction: column e align-items: center.
Per la larghezza: nel layout Desktop, la sezione ha una misura fissa. Non impostarla come width fisso — nella sezione 9 la trasformeremo in layout flessibile. Per ora, concentrati sulla struttura interna.
Per i divisori <hr> (classe .divider), l'<hr> ha stili di default del browser (bordi). Rimuovili con border: none, poi imposta height: 1px, background-color e width: 100%.
3. Form dei filtri
La barra sopra la flashcard contiene controlli per filtrare le card: un dropdown per la categoria, un checkbox "Hide Mastered", e un bottone per applicare. Pensa a:
- Un
<div>basta per raggruppare questi controlli, o c'è un motivo per usare un<form>? Cosa succede se un giorno aggiungi un backend? - Il dropdown mostra "All Categories" come testo iniziale, ma non dovrebbe essere un'opzione selezionabile. Come potresti ottenere questo effetto con un
<select>nativo? - Il bottone per applicare i filtri deve inviare il form. Che
typeuseresti sul<button>? - Le categorie nel dropdown sono raggruppabili (Programming, Computer Science...). Esiste un elemento HTML per raggruppare opzioni in un
<select>?
Placeholder con selected disabled
Il <select> deve mostrare un testo placeholder che non è un'opzione valida:
<option value="" selected disabled>All Categories</option>
selected— visibile di default al caricamentodisabled— non riselezionabile dopo aver scelto un'altra opzionevalue=""— segnala al backend che non è stata fatta una scelta
Raggruppare le opzioni con <optgroup>
Se le categorie sono logicamente raggruppabili, usa <optgroup> con l'attributo label:
<optgroup label="Programming">
<option value="web-development">Web Development</option>
<option value="javascript">JavaScript</option>
</optgroup>
Il browser renderizza il label come intestazione non selezionabile. L'<optgroup> non modifica il funzionamento del form — è puramente un'organizzazione visiva. Non è annidabile: un solo livello di raggruppamento.
Inventa pure le categorie — l'importante è la struttura.
Perché <form> e non <div>?
Avvolgere i filtri in un <form> ha un vantaggio concreto: se domani aggiungi un backend, il form invia i dati nativamente senza JavaScript.
Con method="get" (il default), i parametri finiscono nella URL come query string (es. ?category=css&hide-mastered=true). Questo è ideale per i filtri:
- L'utente può fare bookmark del risultato filtrato
- Il bottone "indietro" del browser riporta ai filtri precedenti
In produzione, method="get" per letture/filtri, method="post" per modifiche di stato. È una distinzione fondamentale.
Struttura HTML del form
Classe: .flashcard-header sul <form>. Contiene:
- Un
<div>con classe.flashcard-filtersper raggruppare i controlli a sinistra - Un
<button type="submit">a destra per applicare i filtri —type="submit"perché deve inviare il form. Contype="button"il click non triggererebbe la submit nativa
Dentro .flashcard-filters:
- Una
<label>per il select (classe.select-label) - Un
<select>con attributinameeid(classe.category-select) - Un checkbox con la sua label
Per il checkbox: la label wrappa il checkbox — classe .checkbox-label sulla <label>. Il checkbox ha name, id e value, e la label ha for che punta all'id. Click sulla label = toggle del checkbox.
Il bottone usa le classi .btn e .btn--primary (variante gialla). Definisci una classe base .btn riutilizzabile per tutti i bottoni dell'app.
Come personalizzare l'aspetto del select?
Il <select> nativo ha un aspetto diverso su ogni browser — la freccia dropdown è impossibile da personalizzare senza un reset.
Il primo passo è appearance: none — rimuove lo stile nativo. Per ricreare la freccia:
background-image: url("./assets/images/icon-chevron-down.svg")background-repeat: no-repeatbackground-position: right 16px centerpadding-rightabbastanza largo (~40px) da non sovrapporre il testo all'icona
Aggiungi bordo, border-radius pill, font-family e font-weight. Un dettaglio: i form element in HTML non ereditano font-family dal body — devi specificarlo esplicitamente.
Come personalizzare l'aspetto del checkbox?
Come per il select, il primo passo è appearance: none. Poi ricostruisci da zero:
- Dimensioni fisse (
widtheheight) - Bordo e
border-radiuspiccolo (non pill — è un quadratino con angoli arrotondati) - Sfondo bianco,
cursor: pointer flex-shrink: 0— impedisce che si restringa in un flex container
Lo stato :checked lo vedremo nella sezione 8.
Come organizzare il layout del form?
Il .flashcard-header usa flexbox con justify-content: space-between (filtri a sinistra, bottone a destra), flex-wrap: wrap e gap. Su schermi stretti, il bottone va a capo.
width: 100% per occupare tutta la sezione, con padding interno.
Anche .flashcard-filters è un flex container con gap, align-items: center e flex-wrap: wrap — label, select e checkbox possono andare a capo indipendentemente.
4. La Flashcard
Osserva la flashcard nel design Figma. È l'elemento più complesso della pagina: ha un colore di sfondo, un pattern SVG sovrapposto, stelle decorative negli angoli, e il contenuto testuale al centro. L'intera card è cliccabile — il click rivela la risposta.
Fermati e prova a rispondere a queste domande prima di continuare:
- L'intera card è cliccabile. Quale elemento HTML sceglieresti? Pensa a cosa succede quando un utente naviga con la tastiera o usa uno screen reader — quale elemento offre quel comportamento gratis?
- Le stelle decorative devono sovrapporsi al contenuto della card, posizionate agli angoli. Come potresti sovrapporre più elementi nello stesso spazio usando CSS Grid?
- Il pattern grafico di sfondo copre tutta la card. Sarebbe meglio un
<img>o una proprietà CSS? Considera che non è contenuto informativo — è puramente decorativo. - In basso c'è una barra che mostra il progresso di padronanza (es. 1/5). Esiste un elemento HTML nativo per rappresentare il completamento di un'attività?
CSS Grid per sovrapposizioni
Qui c'è una tecnica avanzata che abbiamo già incontrato. Le stelle decorative devono sovrapporsi al contenuto della card, posizionate agli angoli. L'approccio è usare CSS Grid per creare layer sovrapposti.
Metti display: grid sul container. Poi, su tutti i figli diretti, imposta:
grid-row: 1;
grid-column: 1;
Questo forza tutti i figli nella stessa cella della griglia — la cella 1,1 — sovrapponendoli. È come un sistema a livelli: ogni figlio è un layer sopra il precedente. L'ordine nel DOM determina quale sta sopra (l'ultimo nel codice sta in cima visivamente), o in alternativa si può usare la proprietà z-index.
Un approccio comodo è usare un selettore generico per tutti i figli diretti:
.flashcard > * { ... }
A questo punto, ogni figlio si posiziona indipendentemente nella cella usando align-self e justify-self. Per esempio, justify-self: end con align-self: start posiziona un elemento nell'angolo in alto a destra della cella. Per spostamenti più fini rispetto agli angoli esatti, aggiungi margin.
Non sai quale elemento HTML usare per la card cliccabile?
La card è un <button> con classe .flashcard e type="button" (non deve submittare nessun form). Perché <button> e non un <div> con un click handler?
- È focusabile da tastiera (Tab) senza aggiungere nulla
- È attivabile con Enter e Space
- Viene annunciato correttamente dagli screen reader come elemento interattivo
Con un <div> dovresti aggiungere manualmente tabindex="0", role="button", e gestire i keypress — tutto lavoro che <button> fa gratis.
La flashcard è wrappata in un <div> con classe .flashcard-container che contiene sia la card che il form delle azioni (sezione 5). Questo container gestisce il padding e la spaziatura tra card e bottoni.
Hai scritto l'HTML della card ma non sai come organizzare i figli per sovrapporli tra loro?
Dentro la flashcard ci sono tre elementi fratelli, tutti figli diretti del <button>:
.flashcard-body: il contenuto vero della card (tag, domanda, hint, progress bar).flashcard-star--top:<img>della stella blu (angolo alto-destra).flashcard-star--bottom:<img>della stella gialla (angolo basso-sinistra)
L'ordine conta: il body sta sotto, le stelle sopra (ultimo nel DOM = in cima visivamente).
Le stelle sono <img> con alt="" (attributo alt vuoto, non assente) — sono decorative, non informative. Un alt vuoto dice agli screen reader di ignorarle. Condividono una classe base .flashcard-star per le proprietà comuni.
Aggiungi pointer-events: none sulle stelle — non devono intercettare i click destinati al <button>. Senza questa proprietà, il click su una stella non attiverebbe il bottone sottostante.
Per posizionare le stelle agli angoli con il grid stacking:
.flashcard-star--top:justify-self: end+align-self: start+marginper l'offset.flashcard-star--bottom:justify-self: start+align-self: end+marginper l'offset
Controlla Figma per le dimensioni delle stelle e l'offset esatto dagli angoli.
Hai bisogno di aiuto con lo sfondo e il pattern della card?
Il pattern è decorativo — non un <img>, ma una proprietà CSS. Usa background-color per il rosa e background-image per il pattern (pattern-flashcard-bg.svg).
Per coprire l'intera card: background-size: 100% 100% allarga il pattern per riempire sia larghezza che altezza. Con background-repeat: no-repeat eviti ripetizioni.
Aggiungi overflow: hidden per ritagliare il pattern agli angoli arrotondati, e un'altezza fissa (controlla Figma).
Un dettaglio che si dimentica spesso: i <button> non ereditano font-family e color dal body — devi impostarli esplicitamente. Aggiungi anche cursor: pointer per comunicare che è cliccabile.
Non sai come organizzare il contenuto interno della card?
Il .flashcard-body è un flex column con align-items: center e gap. Contiene, dall'alto in basso:
-
Tag badge (
.flashcard-tag): uno<span>— un contenitore inline senza significato semantico, ideale quando serve un elemento puramente per lo styling (non è un titolo, non è un paragrafo, non è un link — è un'etichetta visiva). Stile: pill-shape (border-radius alto), sfondo bianco, bordo e un piccolobox-shadowsolido. Font piccolo conletter-spacingnegativo (controlla Figma). -
Area domanda (
.flashcard-question): un<div>che contiene l'<h2>con la domanda (.flashcard-question-text) e un<p>con il suggerimento (.flashcard-hint). Perché un<h2>? Perché la domanda è il titolo della flashcard — il concetto più importante di quel blocco di contenuto. Nella gerarchia degli heading,<h1>è riservato al titolo della pagina, quindi<h2>è il livello giusto per i titoli delle sezioni principali. Il<p>è un paragrafo di testo di supporto — l'elemento corretto per un blocco di testo che accompagna il titolo.Questo container è un flex column centrato sia verticalmente (
justify-content: center) che orizzontalmente (align-items: center). Dovrebbe crescere per occupare lo spazio disponibile — usaflex: 1conwidth: 100%. Il testo della domanda usa il font più grande e pesante dell'app (controlla Figma). L'hint ha un'opacità ridotta per differenziarlo visivamente. -
Progress bar (
.flashcard-progress): un<div>con flex orizzontale e gap, che contiene la barra e il testo "1/5".
Che elemento usare per il "progresso"?
L'elemento <progress> è l'elemento HTML nativo per rappresentare il completamento di un'attività. Due attributi fondamentali:
max— il valore massimo (es.5per 5 livelli di padronanza)value— il valore corrente (es.1)
<progress class="progress-bar" max="5" value="1">1/5</progress>
Il testo dentro <progress> è un fallback per browser che non supportano l'elemento.
Non confondere <progress> con <meter> — <progress> è per attività in corso (completamento), <meter> è per misurazioni scalari (temperatura, uso disco). Qui stiamo mostrando la padronanza come un percorso da 0 a 5, quindi <progress> è corretto.
Styling del <progress>: è uno degli elementi HTML più ostici da personalizzare. Il primo passo è appearance: none per rimuovere lo stile nativo.
Poi servono gli pseudo-elementi vendor — selettori che targettano parti interne dell'elemento specifiche di ciascun browser:
::-webkit-progress-bar— lo sfondo (la "track") su Chrome, Safari, Edge::-webkit-progress-value— la parte riempita (il "fill") su Chrome, Safari, Edge::-moz-progress-bar— la parte riempita su Firefox (Firefox non ha un selettore separato per la track — controlla lo sfondo direttamente sull'elemento<progress>)
Per ciascuno, imposta background-color e border-radius. Sull'elemento <progress> stesso, imposta larghezza, altezza, bordo, border-radius e overflow: hidden.
Il testo accanto (.progress-text) ha lo stesso stile tipografico del tag badge — font piccolo con letter-spacing negativo.
Un consiglio: testa con diversi valori di value (0, 1, 3, 5). I casi value="0" (barra vuota) e value="5" (barra piena) sono quelli dove i problemi di border-radius emergono più facilmente.
5. Azioni sulla card
Sotto la flashcard ci sono due bottoni: "I Know This" (segna la card come padroneggiata) e "Reset Progress" (azzera il progresso). Entrambi modificano lo stato della card. Pensa a:
- Nella sezione 3 hai usato un form con
method="get"per i filtri. Questi bottoni cambiano dati — qualemethodè più appropriato? - Il server deve sapere quale bottone è stato premuto e quale card è stata modificata. Come comunichi queste informazioni senza JavaScript?
- I due bottoni condividono lo stesso
namema hannovaluediversi. Cosa riceve il server quando l'utente clicca uno dei due?
Il pattern dei submit multipli
Questo è un pattern fondamentale per i form con più azioni:
<input type="hidden" name="card-id" value="1" />
<button type="submit" name="action" value="master">I Know This</button>
<button type="submit" name="action" value="reset">Reset Progress</button>
Tre concetti:
<input type="hidden">: inviacard-id=1senza mostrarlo all'utente — il server sa quale card è stata aggiornata, l'utente non ha bisogno di vedere quell'informazione- Due submit con stesso
name: solo il bottone cliccato invia la sua coppia name/value. Il server riceveaction=masteroppureaction=reset— mai entrambi type="submit": entrambi devono esseretype="submit"— contype="button"il click non invierebbe il form
Perché method="post" e non "get"?
Il trade-off: GET mette i parametri nella URL (visibili, bookmarkabili, ripetibili), POST li mette nel corpo della richiesta (non ripetibili con il bottone indietro).
Se "I Know This" usasse GET, l'utente potrebbe premere "Indietro" e ri-eseguire l'azione involontariamente. Con POST, il browser chiede conferma prima di ri-inviare. Per azioni che modificano dati, usa sempre POST.
Struttura HTML completa del form
Classe del form: .flashcard-actions, con method="post".
Ogni bottone contiene un'icona <img> (classe .btn-icon) e il testo. L'ordine nel markup determina l'ordine visivo — icona prima del testo.
Riusa le classi:
.btn .btn--primaryper "I Know This" (sfondo giallo, shadow).btn .btn--shadowper "Reset Progress" (sfondo bianco, shadow)
Hai considerato cosa succede se l'utente preme Enter dentro questo form? Con due submit button, il browser attiva il primo nell'ordine del DOM — "I Know This". Un comportamento ragionevole.
Come definire il sistema di bottoni (classe base + varianti)?
L'approccio è una classe base .btn con tutte le proprietà comuni:
display: flex,align-items: center,justify-content: center,gap- Padding, border-radius pill, bordo
- Font: family, weight, size, line-height, colore testo
- Sfondo bianco,
cursor: pointer,white-space: nowrap
E due classi modificatrici:
.btn--primary:background-colorgiallo,box-shadowcon offset.btn--shadow: solobox-shadowcon offset (sfondo resta bianco)
L'icona (.btn-icon) ha dimensioni fisse e flex-shrink: 0.
Il .flashcard-actions è un flex container centrato (justify-content: center) con gap e flex-wrap: wrap.
6. Navigazione
La barra in fondo alla sezione flashcard contiene tre elementi: un bottone "Previous", un contatore "Card 1 of 40", e un bottone "Next". Pensa a:
- Questa è una zona di navigazione. Quale elemento HTML la rappresenta? È la seconda nella pagina (la prima è nell'header) — c'è qualcosa da considerare quando hai due elementi dello stesso tipo?
- I bottoni Previous e Next hanno l'icona in posizioni diverse (sinistra vs destra del testo). Serve un trick CSS o basta l'ordine nel markup?
- Come centrare il contatore tra i due bottoni usando flexbox?
Scelta degli elementi HTML
Un <nav> con classe .flashcard-nav. Nota: questa è la seconda <nav> nella pagina. Gli screen reader elencano tutti i landmark "navigation" — l'utente non sa distinguerli. Il modo per risolvere è aggiungere un aria-label a ciascuno (es. aria-label="Navigazione principale" sull'header, aria-label="Navigazione flashcard" su questa). È un dettaglio di accessibilità da tenere a mente.
Contiene:
- Bottone "Previous" — icona chevron sinistra prima del testo
<span>con classe.card-counterper "Card 1 of 40" — uno<span>è un contenitore inline senza significato semantico, la scelta giusta per un gancio di stile- Bottone "Next" — icona chevron destra dopo il testo
L'ordine nel markup determina l'ordine visivo — nessun trick CSS necessario.
I bottoni sono type="button" (non inviano form) e usano la classe base .btn senza modificatori — la gerarchia "Border" del design system (solo bordo, niente shadow).
Come centrare il contatore tra i due bottoni?
Il .flashcard-nav usa flexbox con width: 100%, padding, flex-wrap: wrap e gap.
Per centrare il counter: dagli flex: 1 con text-align: center. Il flex: 1 lo espande per occupare tutto lo spazio tra i bottoni, e text-align: center centra il testo internamente.
Su viewport molto strette, con flex-wrap: wrap gli elementi andranno a capo. Non sarà perfetto — ma senza media query è un compromesso accettabile.
Il .card-counter ha font più piccolo e colore meno contrastato (controlla Figma) — è un'informazione secondaria.
7. Sezione Statistiche
La colonna destra (su desktop) mostra quattro card di statistiche: Total Cards, Mastered, In Progress, Not Started. Ogni card ha un'etichetta, un numero e un'area colorata con un'icona. Pensa a:
- Le statistiche sono contenuto principale o complementare? C'è un elemento HTML che distingue il contenuto di supporto dal contenuto dominante?
- Ogni stat card è un blocco di contenuto autonomo e ripetibile. Quale elemento è più appropriato di un
<div>? - Le card devono stare in una colonna quando la sezione è stretta (pannello laterale su desktop) e in due colonne quando è larga (tablet). Come si fa senza media query?
CSS Grid con auto-fill
Qui c'è una tecnica che potresti ricordare. Il problema: il numero di colonne deve adattarsi automaticamente allo spazio disponibile del contenitore — non del viewport.
La proprietà chiave è:
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr))
Scomponiamola:
repeat(auto-fill, ...)— "ripeti la definizione di colonna quante volte ci stanno"minmax(250px, 1fr)— "ogni colonna è larga almeno 250px e al massimo 1fr"
In pratica:
- Container largo 600px → 2 colonne da 300px (250×2=500 < 600: ci stanno)
- Container largo 400px → 1 colonna da 400px (250×2=500 > 400: non ci stanno)
Il browser calcola tutto da solo. Il valore 250px nel minmax è il punto di rottura — sceglilo in base alla larghezza minima a cui una stat card è ancora leggibile.
Un dettaglio: auto-fill vs auto-fit. Con auto-fill, lo spazio extra viene riservato per colonne potenziali (anche vuote). Con auto-fit, le colonne vuote vengono collassate e lo spazio redistribuito. Per questo caso, auto-fill va bene.
Quale elemento per il contenuto complementare? E per le card ripetibili?
Un <aside> con classe .statistics-section — semanticamente appropriato perché le statistiche sono contenuto complementare. Se la sezione flashcard è il "contenuto principale" (dove l'utente studia), le statistiche sono "informazione di supporto" che arricchiscono l'esperienza ma non la definiscono.
Contiene:
- Un
<h2>con classe.statistics-title— stesso livello di heading della domanda nella flashcard, perché entrambi sono titoli di sezioni al medesimo livello nella gerarchia del documento (figli diretti di<main>) - Un
<div>con classe.stats-listper la griglia di card
Ogni stat card (.stat-card) è un <article> — un pezzo di contenuto autonomo e ripetibile. Dentro:
<div class="stat-info">con<span class="stat-label">e<span class="stat-value">. Di nuovo<span>— non<p>— perché sono frammenti brevi, non paragrafi. La regola:<p>per testo discorsivo,<span>per ganci di stile<div class="stat-icon ...">con sfondo colorato e icona<img>
Come stilizzare la sezione e la griglia?
La .statistics-section è un flex column con padding e gap, bordi asimmetrici e border-radius (valori leggermente diversi dalla flashcard section — controlla Figma).
Per .stats-list, usa display: grid con repeat(auto-fill, minmax(250px, 1fr)) e gap.
Come stilizzare la singola stat card?
La .stat-card è un flex container orizzontale con due aree:
.stat-info: flex column conflex: 1, padding egaptra label e valore. Il.stat-labelusa Medium 16px, il.stat-valueusa Bold 40px (controlla Figma).stat-icon: larghezza fissa (~100px),flex-shrink: 0, bordo sinistro come divisore, sfondo colorato, contenuto centrato con flex
Classi modificatrici BEM per i colori: .stat-icon--blue, .stat-icon--teal, .stat-icon--pink, .stat-icon--magenta. Ognuna imposta solo background-color.
Le card hanno bordi asimmetrici (spessori diversi dalle sezioni — controlla Figma) e border-radius. L'overflow: hidden è importante: senza, il colore di sfondo dell'icona "esce" dagli angoli arrotondati.
8. Stati interattivi
Ogni elemento interattivo dell'interfaccia deve dare feedback visivo all'utente. Il hover comunica "questo è cliccabile", il focus comunica "sei qui con la tastiera", lo stato checked comunica "questo è selezionato". Senza questi feedback, l'interfaccia sembra statica.
Il file Figma contiene varianti dei componenti per ogni stato. Cercale: _Tab button base, Button (con le gerarchie Primary, Secondary, Border), _Checkbox base. Prova a:
- Ispezionare le differenze visive tra Default, Hover e Focus per ciascun componente. Cosa cambia? È il colore, il bordo, il shadow?
- Capire come creare un doppio anello di focus (focus ring) usando solo
box-shadow - Pensare a quale pseudo-classe usare per il focus:
:focuso:focus-visible? Qual è la differenza? - Per il checkbox, immaginare tutte le combinazioni di stato: unchecked + hover, checked + hover, checked + focus...
Il focus ring con box-shadow multipli
Il pattern di focus è coerente in tutta l'app: un doppio anello ottenuto con box-shadow multipli:
box-shadow: 0 0 0 Xpx white, 0 0 0 Ypx <colore>
box-shadow accetta valori multipli separati da virgola. Entrambi hanno offset 0 e blur 0, ma spread diverso:
- Il primo crea un anello bianco stretto attorno all'elemento
- Il secondo crea un anello colorato più largo, visibile oltre quello bianco
Il risultato è un doppio ring (colorato → bianco → bordo dell'elemento) — un pattern di accessibilità che garantisce visibilità su qualsiasi sfondo.
:focus-visible vs :focus
La differenza è importante:
:focussi attiva sempre — incluso dopo un click del mouse:focus-visiblesi attiva solo durante la navigazione da tastiera (Tab)
Con :focus vedresti il ring anche dopo ogni click. Con :focus-visible, solo quando serve davvero. Aggiungi outline: none per rimuovere l'outline di default del browser.
Quali proprietà cambiano al hover per ciascun componente?
- Tab inattivo (
.tab-button:hover): il bordotransparentdiventa visibile. Il tab attivo non cambia — ha già il bordo - Button — Border (
.btn:hover):background-colorleggermente più scuro - Button — Primary (
.btn--primary:hover):box-shadowcresce (offset maggiore) — il bottone sembra "alzarsi" - Button — Secondary (
.btn--shadow:hover): stessa cosa — shadow cresce - Checkbox (
.checkbox:hover): piccolobox-shadowsolido - Select (
.category-select:hover):background-colorleggermente più scuro
Prendi i valori esatti da Figma. Un dettaglio sulla cascade: siccome tutti gli hover sono nel layer states, .btn--primary:hover vince su .btn:hover per specificità (due classi vs una), indipendentemente dall'ordine nel file.
Come implementare il focus ring sui diversi componenti?
Per tab, checkbox, select e bottoni Border: usa il doppio ring con box-shadow. Ricordati outline: none.
Per bottoni Primary e Secondary: controlla su Figma se il focus usa un shadow direzionale (con offset, ma di colore diverso — es. blu) anziché il ring circolare. In produzione si mantiene coerenza, ma segui il design.
Come gestire lo stato :checked del checkbox e le sue combinazioni?
Quando il checkbox è spuntato:
background-colordiventa giallo- Appare l'icona check come
background-image(usaicon-check.svg) background-size,background-position: center,background-repeat: no-repeat
Nota: appearance: none rimuove lo stile nativo ma il checkbox mantiene il comportamento — resta toggabile con click e Space.
Le combinazioni da coprire:
.checkbox:checked— sfondo giallo + icona.checkbox:checked:hover— + box-shadow hover.checkbox:checked:focus-visible— + focus ring
Se il focus ring sovrascrive il box-shadow dell'hover (entrambi usano la stessa proprietà), il focus ring vince — è un indicatore di accessibilità.
9. Layout responsivo (senza media query)
Il Figma mostra tre breakpoint (Desktop, Tablet, Mobile), ma non useremo media query. L'idea è diversa: non dire al browser quando cambiare layout, ma dargli regole flessibili che lo facciano adattare da solo.
Confronta i tre breakpoint su Figma e prova a identificare:
- Cosa cambia tra desktop e tablet nel layout principale? Le due sezioni (flashcard e statistiche) passano da affiancate a impilate. Come potresti ottenere questo con
flex-wrap? - Cosa controlla il "punto di rottura" — cioè a che larghezza le sezioni smettono di stare affiancate? Che ruolo ha
flex-basisin questo? - La griglia delle stat card nella sezione 7 si adatta già? Se hai usato
auto-fill, dovrebbe farlo automaticamente. Verifica. - Quali differenze tra i breakpoint non puoi replicare senza media query?
flex-wrap con flex-basis
Questa tecnica permette a due elementi di stare affiancati quando c'è spazio e impilarsi quando non ce n'è — senza breakpoint espliciti.
Scomponiamo flex: 1 1 500px:
flex-grow: 1— l'elemento cresce per occupare spazio extraflex-shrink: 1— si restringe se necessarioflex-basis: 500px— la dimensione "ideale" prima che grow/shrink entrino in gioco
Se due flex items con basis 500px e 300px sono in un container da 900px: 500 + 300 + gap ≈ 832px < 900, stanno entrambi su una riga. In un container da 700px: 832 > 700, non ci stanno — con flex-wrap: wrap, il secondo va a capo.
Il flex-basis è il punto di "rottura implicito". Scegli valori che riflettano la larghezza minima a cui ogni sezione funziona bene.
Come applicare il wrapping al layout principale?
- Sul
.main-content: aggiungiflex-wrap: wrap(ildisplay: flexe ilgapli hai già) - Sulla
.flashcard-section: sostituisci la larghezza fissa conflex: 1 1 500px - Sulla
.statistics-section: sostituisci la larghezza fissa conflex: 1 1 300px
Aggiungi min-width: 0 su entrambe — previene un bug di flexbox dove un flex item non può essere più stretto del suo contenuto.
La classe .container della sezione 0 gestisce già il centraggio e il padding laterale su tutte le viewport. max-width + padding + margin: auto fa tutto da solo.
Le stat card in griglia si adattano già?
Se hai usato repeat(auto-fill, minmax(250px, 1fr)) per .stats-list:
- Su desktop (stats nel pannello laterale, ~344px di contenuto): 250×2=500 > 344 → 1 colonna
- Su tablet (stats a tutta larghezza, ~700px): 250×2=500 < 700 → 2 colonne
La griglia risponde alla larghezza del suo contenitore, non del viewport. Nessun codice aggiuntivo.
Dove aggiungere flex-wrap agli altri componenti?
Aggiungi flex-wrap: wrap (con gap) a:
.flashcard-header— filtri a sinistra, bottone a destra; su schermi stretti il bottone va a capo.flashcard-filters— label, select e checkbox possono andare a capo indipendentemente.flashcard-actions— i bottoni azione si impilano quando non c'è spazio.flashcard-nav— con.card-counterimpostato aflex: 1etext-align: center.header— logo e tab-nav possono andare a capo
Cosa non riesci a fare senza media query?
Questo approccio è potente ma ha limiti concreti:
- Cambiare font-size — il titolo a 40px resta 40px ovunque
- Nascondere/mostrare testo — i bottoni Previous/Next mostrano sempre il testo, anche su mobile
- Bottoni full-width su mobile — mantengono la larghezza naturale
- Cambiare gap e padding — spaziature compatte su mobile richiederebbero una media query
- Riordinare gli elementi — servirebbe
orderdentro una media query - Cambiare direzione di layout — es. stat card verticale su mobile
- Scambiare un'immagine — es. logo diverso su mobile (servirebbe
<picture>con media query)
Queste sono le situazioni in cui servirebbero le media query — le imparerai prossimamente. Per ora, il layout si comporta ragionevolmente su tutte le dimensioni grazie alle proprietà intrinseche.
Ordine consigliato di sviluppo
Un approccio che funziona bene:
-
Prima l'HTML, tutto: scrivi l'intera struttura HTML senza CSS. Apri la pagina nel browser — dovrebbe essere brutta ma funzionale: i link sono cliccabili, il form si può compilare, il select mostra le opzioni, il checkbox si può spuntare. Se il contenuto è accessibile senza CSS, la struttura è corretta
-
Poi il CSS, per sezione: parti dalla dichiarazione
@layer, poi le@font-face, poi procedi layer per layer —reset,base,layout,components. Dopo ogni componente, verifica nel browser -
Il layer
statesper ultimo: aggiungi gli stati interattivi (hover, focus, checked) nel layerstatessolo dopo che layout e componenti funzionano correttamente -
Il layout responsivo come ultimo step: le proprietà
flex-wrap,flex-basiseauto-fillvanno aggiunte alla fine, quando tutti i componenti sono già stilizzati. Altrimenti stai cercando di risolvere due problemi contemporaneamente
Riepilogo delle tecniche utilizzate
In questa esercitazione hai esplorato:
@layerper architettura CSS — dichiarare l'ordine dei layer (reset,base,layout,components,states) rende la cascade esplicita e prevedibile, eliminando i problemi di ordine nel file- Il pattern
.container(max-width+padding+margin: 0 auto) — il modo standard per centrare contenuto e renderlo responsivo senza media query - CSS Grid per sovrapposizioni (
grid-row: 1; grid-column: 1su più figli) — un'alternativa per sovrapporre elementi senza posizionamento assoluto repeat(auto-fill, minmax())— griglie che si adattano automaticamente al numero di colonne in base allo spazio disponibileflex-wrapconflex-basis— layout che passano da affiancato a impilato in base alla larghezza del container- L'elemento
<progress>con i suoi pseudo-elementi vendor (::-webkit-progress-bar,::-webkit-progress-value,::-moz-progress-bar) <optgroup>per raggruppare opzioni in un<select>selected disabledper creare placeholder nei<select>appearance: noneper resettare lo stile nativo di select, checkbox e progress bar:focus-visibleper focus ring che appaiono solo con navigazione da tastiera- Double-ring focus con
box-shadowmultipli (spread senza blur) - Form semantici con
method(GET vs POST),name,value,<input type="hidden">, e la distinzione tratype="submit"etype="button" - Bordi asimmetrici con
border-widtha quattro valori overflow: hiddenper ritagliare contenuti figlio ai bordi arrotondati del genitore- Naming BEM con il pattern
blocco,blocco--modificatoreper varianti leggibili
Hai costruito un'interfaccia completa — con filtri, card interattiva, navigazione e statistiche — che si adatta a diverse dimensioni di schermo usando esclusivamente proprietà intrinseche del CSS. Sono concetti che userai costantemente in qualsiasi progetto web professionale.
La prossima volta che vedrai un sito o una web app, prova a identificare quali di queste tecniche vengono usate — il pattern container, i bordi asimmetrici, i focus ring, le griglie responsive. Ti accorgerai che sono ovunque.