1. Struttura di base HTML5 Moderno
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="strict-origin-when-cross-origin">
<title>Titolo Ottimizzato SEO</title>
<meta name="description" content="Descrizione accattivante per i risultati di ricerca.">
<link rel="canonical" href="https://example.com/pagina">
<!-- Preconnessione ai font esterni -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Stili -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main>
<h1>Contenuto Principale</h1>
</main>
</body>
</html>
2. Layout Semantico e Accessibile
<header role="banner">
<nav aria-label="Navigazione principale">
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li><a href="/servizi">Servizi</a></li>
</ul>
</nav>
</header>
<main id="main-content" role="main">
<article>
<header>
<h1>Titolo dell'Articolo</h1>
<p>Pubblicato il <time datetime="2026-07-04">4 Luglio 2026</time></p>
</header>
<section aria-labelledby="sec-intro">
<h2 id="sec-intro">Introduzione</h2>
<p>Contenuto descrittivo...</p>
</section>
</article>
<aside aria-label="Risorse correlate">
<h3>Link utili</h3>
</aside>
</main>
<footer role="contentinfo">
<p>© 2026 Codedge. Tutti i diritti riservati.</p>
</footer>
3. Form di Contatto Strutturato
<form action="/submit" method="POST" class="form-container" novalidate>
<div class="form-group">
<label for="user-name">Nome Completo</label>
<input type="text" id="user-name" name="name" required minlength="3" autocomplete="name">
</div>
<div class="form-group">
<label for="user-email">Indirizzo Email</label>
<input type="email" id="user-email" name="email" required autocomplete="email">
</div>
<div class="form-group">
<label for="user-message">Messaggio</label>
<textarea id="user-message" name="message" rows="5" required minlength="10" placeholder="Scrivi qui..."></textarea>
</div>
<button type="submit" class="submit-btn">Invia Richiesta</button>
</form>
4. Immagine Responsive con Picture e WebP
<picture>
<!-- Schermi grandi (desktop) fallback WebP -->
<source media="(min-width: 1024px)" srcset="image-large.webp" type="image/webp">
<source media="(min-width: 1024px)" srcset="image-large.jpg" type="image/jpeg">
<!-- Schermi piccoli (mobile) fallback WebP -->
<source srcset="image-small.webp" type="image/webp">
<!-- Immagine di fallback standard -->
<img src="image-small.jpg" alt="Descrizione dell'immagine" loading="lazy" decoding="async" width="600" height="400">
</picture>
5. Iframe Video Responsivo (Anti-CLS)
<div class="video-container">
<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ"
title="Video di esempio"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
loading="lazy">
</iframe>
</div>
<style>
.video-container {
position: relative;
width: 100%;
aspect-ratio: 16 / 9; /* Risolve il problema del CLS senza padding-top hack */
}
.video-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 8px;
}
</style>
1. Reset CSS Moderno
/* Reset basato sulle best practice moderne */
*, *::before, *::after {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #ffffff;
color: #1a1a1a;
text-rendering: optimizeLegibility;
}
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
height: auto;
}
input, button, textarea, select {
font: inherit;
}
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
html:focus-within {
scroll-behavior: smooth;
}
2. Centratura e Allineamento Flexbox
/* Centratura perfetta in un contenitore */
.flex-center {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
/* Row flessibile e responsiva con gap */
.flex-row {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
justify-content: space-between;
align-items: center;
}
/* Forza la colonna flex su piccoli schermi */
@media (max-width: 640px) {
.flex-row {
flex-direction: column;
align-items: stretch;
}
}
3. Griglia Auto-Responsive (Senza Media Queries)
/* Genera colonne dinamiche in base allo spazio disponibile */
.auto-grid {
display: grid;
/* Minimo 280px, massimo 1 parte dello spazio rimanente */
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
}
/* Griglia per layout asimmetrici (Sidebar 300px + Main flessibile) */
.layout-grid {
display: grid;
grid-template-columns: 300px minmax(0, 1fr);
gap: 2rem;
}
@media (max-width: 768px) {
.layout-grid {
grid-template-columns: 1fr;
}
}
4. Variabili CSS e Dark Mode nativa
/* Root per tema chiaro di base */
:root {
--primary-color: #007bf0;
--bg-color: #ffffff;
--text-color: #1a1a1a;
--border-color: #e5e7eb;
}
/* Attivazione dark mode nativa del sistema */
@media (prefers-color-scheme: dark) {
:root {
--primary-color: #3b82f6;
--bg-color: #111827;
--text-color: #f9fafb;
--border-color: #374151;
}
}
/* Utilizzo pratico delle custom properties */
body {
background-color: var(--bg-color);
color: var(--text-color);
border-color: var(--border-color);
}
5. Hover ed Effetti UI
/* Effetto bottone lucido con transizione smooth */
.btn-interactive {
position: relative;
display: inline-flex;
padding: 0.75rem 1.5rem;
background-color: var(--primary-color, #007bf0);
color: #fff;
border-radius: 6px;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
}
.btn-interactive:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0, 123, 240, 0.25);
background-color: #0060be;
}
.btn-interactive:active {
transform: translateY(1px);
}
6. Carosello CSS con Scroll Snap
/* Contenitore a scorrimento orizzontale magnetico */
.carousel-snap {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
gap: 1rem;
padding: 1rem 0;
}
/* Singolo elemento del carosello */
.carousel-item {
flex: 0 0 80%; /* 80% della larghezza del parent */
scroll-snap-align: center;
border-radius: 8px;
background: #2a2a2a;
}
7. Troncamento Testo Multilinea
/* Limita il testo a un numero esatto di righe con ellissi (...) */
.truncate-lines-3 {
display: -webkit-box;
-webkit-line-clamp: 3; /* Numero massimo di righe */
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
/* Singolo troncatore classico su una sola riga */
.single-line-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
1. Event Delegation su elementi dinamici
// Ascolta i click sull'intero container per intercettare bottoni generati al volo
const container = document.querySelector('.dynamic-list');
container.addEventListener('click', (event) => {
// Trova l'elemento target con classe specifica
const deleteBtn = event.target.closest('.btn-delete');
if (deleteBtn) {
const item = deleteBtn.closest('.list-item');
item.remove(); // Rimuove l'elemento corrispondente
}
});
2. Ottimizzazioni Debounce e Throttle
// Debounce: esegue la funzione solo dopo N millisecondi di inattività
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// Throttle: esegue la funzione al massimo una volta ogni N millisecondi
function throttle(fn, limit) {
let inThrottle = false;
return function (...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Esempio: throttle sullo scroll o debounce sulla ricerca input
window.addEventListener('scroll', throttle(() => console.log('Scrolled!'), 200));
3. Chiamata Fetch API con Async/Await
async function fetchData(url = '') {
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Errore HTTP: status ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Errore durante la chiamata fetch:', error);
return null;
}
}
4. LocalStorage JSON Helper
const storage = {
// Salva un oggetto convertendolo in stringa JSON
set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (e) {
console.error('Errore scrittura LocalStorage:', e);
return false;
}
},
// Recupera l'oggetto deserializzandolo
get(key) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : null;
} catch (e) {
console.error('Errore lettura LocalStorage:', e);
return null;
}
}
};
5. Intersection Observer per Lazy/Animations
// Crea un observer per monitorare la visibilità degli elementi nello schermo
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible'); // Aggiunge la classe per avviare transizioni CSS
observer.unobserve(entry.target); // Smette di osservare l'elemento una volta attivato
}
});
}, {
threshold: 0.15, // Attiva l'azione al 15% di visibilità dell'elemento
rootMargin: '0px 0px -50px 0px' // Margine inferiore per anticipare l'azione
});
// Avvia l'osservatore su tutti i target stabiliti
document.querySelectorAll('.animate-on-scroll').forEach(el => observer.observe(el));
2. Card contenuto riusabile
<article class="content-card" data-content-card>
<div class="content-card__topline">
<span class="content-card__badge">Guida</span>
<button class="content-card__save" type="button" data-card-save aria-pressed="false">
Salva
</button>
</div>
<h2>Accessibilita web base</h2>
<p>Checklist essenziale per focus, testi alternativi, contrasto e navigazione da tastiera.</p>
<dl class="content-card__meta">
<div><dt>Tempo</dt><dd>18 min</dd></div>
<div><dt>Livello</dt><dd>Base</dd></div>
</dl>
<a class="content-card__link" href="#">Apri risorsa</a>
</article>
<style>
.content-card {
display: flex;
flex-direction: column;
gap: 1rem;
max-width: 360px;
padding: 1rem;
border: 1px solid #d8dde6;
border-top: 4px solid #14b8a6;
border-radius: 8px;
background: #fff;
color: #172033;
}
.content-card__topline {
display: flex;
justify-content: space-between;
gap: .75rem;
align-items: center;
}
.content-card__badge {
color: #64748b;
font-size: .75rem;
font-weight: 800;
text-transform: uppercase;
}
.content-card__save {
border: 1px solid #cbd5e1;
border-radius: 999px;
background: #fff;
cursor: pointer;
padding: .45rem .7rem;
}
.content-card__save[aria-pressed="true"] {
background: #fff7ed;
border-color: #fb923c;
color: #c2410c;
}
.content-card h2 { margin: 0; font-size: 1.35rem; line-height: 1.2; }
.content-card p { margin: 0; color: #475569; line-height: 1.6; }
.content-card__meta {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: .65rem;
margin: auto 0 0;
}
.content-card__meta div { background: #f8fafc; border-radius: 6px; padding: .7rem; }
.content-card__meta dt { color: #64748b; font-size: .72rem; font-weight: 800; text-transform: uppercase; }
.content-card__meta dd { margin: .2rem 0 0; font-weight: 800; }
.content-card__link {
display: inline-flex;
justify-content: center;
border-radius: 6px;
border: 1px solid #0f766e;
background: #123743;
color: #f8fbff;
font-weight: 800;
padding: .75rem .9rem;
text-decoration: none;
}
.content-card__link:hover,
.content-card__link:focus-visible {
background: #a8f0df;
border-color: #0f766e;
color: #10201f;
}
</style>
<script>
document.querySelectorAll('[data-card-save]').forEach((button) => {
button.addEventListener('click', () => {
const active = button.getAttribute('aria-pressed') === 'true';
button.setAttribute('aria-pressed', String(!active));
});
});
</script>
3. Header responsive accessibile
<nav class="site-header" data-accessible-header aria-label="Navigazione principale">
<a class="site-header__brand" href="/">Brand</a>
<button class="site-header__toggle" type="button" data-header-toggle
aria-controls="site-header-menu" aria-expanded="false">
Menu
</button>
<ul class="site-header__menu" id="site-header-menu" data-header-menu>
<li><a href="/" data-header-link aria-current="page">Home</a></li>
<li><a href="/risorse/" data-header-link>Risorse</a></li>
<li><a href="/progetti/" data-header-link>Progetti</a></li>
<li><a href="/contatti/" data-header-link>Contatti</a></li>
</ul>
</nav>
<style>
.site-header {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
min-height: 4rem;
padding: .85rem 1rem;
background: #172033;
color: #fff;
}
.site-header__brand,
.site-header__menu a {
color: inherit;
text-decoration: none;
}
.site-header__brand { font-weight: 900; }
.site-header__menu {
display: flex;
gap: .25rem;
align-items: center;
list-style: none;
margin: 0;
padding: 0;
}
.site-header__menu a {
display: inline-flex;
border-radius: 999px;
padding: .55rem .75rem;
font-weight: 700;
}
.site-header__menu a:hover,
.site-header__menu a:focus-visible,
.site-header__menu a[aria-current="page"] {
background: rgba(255, 255, 255, .12);
}
.site-header__toggle {
display: none;
border: 1px solid rgba(255, 255, 255, .24);
border-radius: 999px;
background: transparent;
color: inherit;
cursor: pointer;
font: inherit;
font-weight: 800;
padding: .55rem .75rem;
}
.site-header a:focus-visible,
.site-header button:focus-visible {
outline: 2px solid #67e8f9;
outline-offset: 3px;
}
@media (max-width: 720px) {
.site-header { flex-wrap: wrap; }
.site-header__toggle { display: inline-flex; }
.site-header__menu {
display: none;
flex-basis: 100%;
flex-direction: column;
align-items: stretch;
padding-top: .75rem;
}
.site-header.is-open .site-header__menu { display: flex; }
.site-header__menu a { border-radius: 6px; width: 100%; }
}
</style>
<script>
function initAccessibleHeader(selector = '[data-accessible-header]') {
const header = document.querySelector(selector);
if (!header) return;
const toggle = header.querySelector('[data-header-toggle]');
const links = [...header.querySelectorAll('[data-header-link]')];
function setOpen(open) {
header.classList.toggle('is-open', open);
toggle.setAttribute('aria-expanded', String(open));
}
toggle.addEventListener('click', (event) => {
event.stopPropagation();
setOpen(toggle.getAttribute('aria-expanded') !== 'true');
});
links.forEach((link) => link.addEventListener('click', () => setOpen(false)));
document.addEventListener('click', (event) => {
if (!header.contains(event.target)) setOpen(false);
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') setOpen(false);
});
}
initAccessibleHeader();
</script>
4. Form contatto con validazione
<form class="contact-form" data-contact-form novalidate>
<div class="field">
<label for="name">Nome</label>
<input id="name" name="name" required minlength="2" data-validate="name"
aria-describedby="name-error" aria-invalid="false">
<p class="error" id="name-error"></p>
</div>
<div class="field">
<label for="email">Email</label>
<input id="email" name="email" type="email" required data-validate="email"
aria-describedby="email-error" aria-invalid="false">
<p class="error" id="email-error"></p>
</div>
<div class="field">
<label for="message">Messaggio</label>
<textarea id="message" name="message" required minlength="12" data-validate="message"
aria-describedby="message-error" aria-invalid="false"></textarea>
<p class="error" id="message-error"></p>
</div>
<button type="submit" data-submit-button>Invia richiesta</button>
<p class="status" data-form-status role="status" aria-live="polite"></p>
</form>
<style>
.contact-form {
display: grid;
gap: 1rem;
max-width: 520px;
padding: 1rem;
border: 1px solid #d8dde6;
border-radius: 8px;
}
.field { display: grid; gap: .4rem; }
.field label { font-weight: 800; }
.field input,
.field textarea {
width: 100%;
border: 1px solid #cbd5e1;
border-radius: 6px;
font: inherit;
padding: .75rem .8rem;
}
.field textarea { min-height: 8rem; resize: vertical; }
.field input:focus,
.field textarea:focus {
border-color: #0ea5e9;
box-shadow: 0 0 0 3px rgba(14, 165, 233, .18);
outline: 0;
}
.field [aria-invalid="true"] { border-color: #ef4444; }
.error { min-height: 1.2rem; margin: 0; color: #b91c1c; font-size: .9rem; }
.status { margin: 0; color: #047857; font-weight: 700; }
button[type="submit"] {
border: 0;
border-radius: 6px;
background: #0f172a;
color: #fff;
cursor: pointer;
font: inherit;
font-weight: 800;
padding: .8rem 1rem;
}
button[disabled] { cursor: wait; opacity: .7; }
</style>
<script>
function initValidatedContactForm(selector = '[data-contact-form]') {
const form = document.querySelector(selector);
if (!form) return;
const fields = [...form.querySelectorAll('[data-validate]')];
const status = form.querySelector('[data-form-status]');
const submit = form.querySelector('[data-submit-button]');
function getMessage(field) {
const value = field.value.trim();
if (field.required && !value) return 'Campo obbligatorio.';
if (field.type === 'email' && !field.validity.valid) return 'Inserisci una email valida.';
if (field.minLength > 0 && value.length < field.minLength) {
return 'Inserisci almeno ' + field.minLength + ' caratteri.';
}
return '';
}
function validate(field) {
const error = document.getElementById(field.id + '-error');
const message = getMessage(field);
field.setAttribute('aria-invalid', message ? 'true' : 'false');
if (error) error.textContent = message;
return !message;
}
fields.forEach((field) => {
field.addEventListener('input', () => validate(field));
field.addEventListener('blur', () => validate(field));
});
form.addEventListener('submit', (event) => {
event.preventDefault();
const valid = fields.map(validate).every(Boolean);
if (!valid) {
fields.find((field) => field.getAttribute('aria-invalid') === 'true')?.focus();
status.textContent = 'Controlla i campi evidenziati.';
return;
}
submit.disabled = true;
submit.textContent = 'Invio in corso';
status.textContent = "Messaggio pronto per l'invio.";
});
}
initValidatedContactForm();
</script>