The Design of Everyday Things
Il Libro
Questo è stato in assoluto il primo libro di UX che ho letto e lo reputo tuttora il manuale operativo fondamentale per comprendere la psicologia dietro l'interazione uomo-macchina. Norman smonta la convinzione che l'errore sia colpa dell'utente, dimostrando come ogni scivolone sia in realtà il risultato di una cattiva progettazione. Attraverso concetti come Affordance, Mapping e Feedback, il libro trasforma il design da un esercizio puramente estetico a una scienza comportamentale. È stato fondamentale per me, perché credo che un UX Engineer non debba limitarsi a decorare interfacce, bensì progettare percorsi mentali in cui l'utente non debba mai chiedersi come si usa un oggetto o un componente.
Esempio Pratico: "The State Machine Button"
Per dimostrare i principi di Norman in un contesto puramente software, ho progettato e sviluppato un componente Button "consapevole". Troppo spesso, nei siti web e nelle app (meno nell'ecosistema Apple grazie agli standard imposti dalle Human Interface Guidelines), i bottoni sono rettangoli statici che cambiano colore solo al click, violando il principio di Feedback continuo. Ho creato un componente che rispetta il ciclo di azione completo descritto da Norman. L'Affordance è garantita da un'elevazione visiva data dall'ombra che suggerisce la cliccabilità, mentre il Feedback è immediato e attraversa cinque stadi distinti: riposo, intenzione, azione, attesa e conferma. Questo assicura che lo spazio tra l'azione dell'utente e la comprensione del risultato sia ridotto a zero.

Prototipo Figma
Implementazione Reale (Codice) • Velocità 0.5x
Codice Sorgente
Per quanto riguarda l'implementazione, ho utilizzato HTML semantico, CSS moderno con Custom Properties per la gestione degli stati e Vanilla JavaScript con Arrow Functions per la logica di interazione.
- index.html
- styles.css
- script.js
<!--
DESIGN
------
* Semantic structure and accessibility first:
* - Semantic tag: I chose <button> over <div> to gain native keyboard
* focus and screen reader support (Affordance)
* - ARIA integration: aria-live="polite" is critical here. It ensures
* that the feedback (loading/success) is announced to non-visual users
* - State readiness: The .loader is present in the DOM but hidden,
* ready to appear without causing layout shifts.
-->
<div class="button-container">
<button id="action-btn" class="norman-btn" aria-live="polite">
<span class="btn-text">Confirm action</span>
<span class="loader"></span>
</button>
</div>
<link rel="stylesheet" href="styles.css">
<script src="script.js"></script>
/* DESIGN
------
* Physics metaphor and state management.
* - Token-based depth: Shadows represent the Z-axis (elevation)
* I defined them in :root to maintain consistent "physical laws"
* across the application
* - Interaction physics: The transition is tuned to mimic a mechanical
* spring (ease), providing realistic tactile feedback
* - Constraint styling: The 'loading' state enforces the constraint
* visually (opacity) and functionally (pointer-events: none)
* - Layout stability: I use CSS Grid Stack technique here. Instead of
* absolute positioning, I place both text and loader in the same
* Grid cell (1x1). This guarantees mathematical centering.
*/
/* * Here I manage the "Button physics".
* The shadow is not decorative but functional, as it suggests
* elevation (Affordance)
* When pressed (:active), the shadow decreases and the element goes down,
* simulating real mechanical resistance (Tactile feedback).
*/
:root {
/* Colors */
--primary-color: #ffd500;
--hover-color: #d5bb3b;
--bg-body: #524236;
--btn-text: #000000;
/* Shapes and typography */
--btn-radius: 14px;
--btn-weight: 550;
/* Physics */
--shadow-rest: 0 8px 10px -1px rgba(0, 0, 0, 0.4);
--shadow-active: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
body {
background-color: var(--bg-body);
/* Viewport Centering */
margin: 0;
height: 100vh; /* Full viewport height */
display: grid;
place-items: center; /* Perfect centering */
}
.norman-btn {
/* GRID STACKING TECHNIQUE: Perfect centering without magic numbers */
display: inline-grid;
place-items: center;
grid-template-areas: "stack";
position: relative;
padding: 0.75rem 1.5rem;
min-width: 160px; /* Prevents width collapse during state change */
border: none;
border-radius: var(--btn-radius);
background-color: var(--primary-color);
color: var(--btn-text);
font-weight: var(--btn-weight);
box-shadow: var(--shadow-rest); /* Affordance: elevation */
cursor: pointer;
transition: all 0.1s ease;
}
/* Stacking both elements in the same grid cell */
.btn-text, .loader {
grid-area: stack;
transition: opacity 0.2s ease;
}
.norman-btn:hover {
background-color: var(--hover-color);
transform: translateY(-1px); /* Intention: it moves closer to the finger/cursor */
}
.norman-btn:active {
transform: translateY(2px); /* Action: it sinks under pressure */
box-shadow: var(--shadow-active);
}
/* STATE MANAGEMENT: LOADING */
.norman-btn.loading {
cursor: wait;
opacity: 0.8;
pointer-events: none; /* Constraint: prevents double clicks */
/* UX physics: the button stays "pressed" during work */
box-shadow: none; /* Removes elevation (flat against surface) */
transform: translateY(2px); /* Keeps the pressed position */
background-color: var(--hover-color); /* Maintains the active/pressed color */
}
.loader {
/* Visibility handled by opacity for smooth transition */
visibility: hidden;
opacity: 0;
width: 1rem;
height: 1rem;
/* Loader color inherits from text color token for consistency */
border: 2px solid var(--btn-text);
border-bottom-color: transparent;
border-radius: 50%;
animation: rotation 1s linear infinite;
}
.norman-btn.loading .btn-text {
visibility: hidden;
opacity: 0;
}
.norman-btn.loading .loader {
visibility: visible;
opacity: 1;
}
@keyframes rotation {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* DESIGN
------
* State logic and async simulation.
* - Event-driven architecture: The logic reacts to user intent immediately
* - Constraint enforcement: The guard clause prevents "Rage clicks" or
* double submissions by checking the current state
* - Feedback loop: Simulates network latency providing visual
* feedback throughout the entire lifecycle.
*/
/*
I use arrow functions to manage the logic. On click, the button enters
a "Loading" state (constraint) preventing further accidental clicks,
then provides final feedback (success).
*/
const btn = document.getElementById('action-btn');
const handleClick = () => {
// Constraint: prevent multiple clicks
if (btn.classList.contains('loading')) return;
// Feedback: enter the waiting state
btn.classList.add('loading');
// Simulation of an asynchronous call (e.g., API)
setTimeout(() => {
// Feedback: end operation and return to resting state
btn.classList.remove('loading');
alert("Action completed!");
}, 2000);
};
btn.addEventListener('click', handleClick);
Micro-Patterns
Ho isolato altri concetti chiave dai miei appunti ed ho provato a tradurli in pattern di sviluppo.
Vincoli e Prevenzione degli Errori
Norman insegna che è meglio impedire l'errore alla radice piuttosto che curarlo dopo. Invece di mostrare fastidiosi messaggi di errore come "Data non valida" dopo che l'utente ha già cliccato, userò i vincoli nativi dell'HTML5. Ad esempio, impostando gli attributi min, max e required direttamente nell'input, guido l'utente verso l'azione corretta in modo invisibile, sfruttando i vincoli logici per rendere impossibile l'inserimento di dati errati.
Slips vs. Mistakes
Norman fa una distinzione veramente interessante tra Slips (scivoloni) e Mistakes (errori cognitivi).
Uno Slip è un errore di esecuzione: volevo cliccare 'Modifica' ma il mouse è "scivolato" su 'Elimina' per distrazione.
Un Mistake, invece, è un errore di pianificazione: ho eseguito l'azione corretta, ma il mio modello mentale era sbagliato (pensavo che quel bottone facesse un'altra cosa).
Ho capito che, specialmente per gli Slips, la soluzione non è punire l'utente bloccando il flusso con fastidiosi 'Sei sicuro?', ma offrirgli una via d'uscita elegante.
D'ora in poi, ove possibile, sostituirò la conferma preventiva con un sistema di Undo (Annulla) non intrusivo.
Ecco come immagino questa logica di 'recupero' gestita via codice:
// Pattern "Undo" che userò al posto delle modali di conferma
const deleteItem = (itemId) => {
// Rimuovo l'item dalla vista immediatamente (Feedback istantaneo)
removeItemFromDOM(itemId);
// Mostro il Toast (una notifica temporanea non intrusiva) con l'opzione Undo
showToast({
message: "Elemento eliminato",
actionText: "Annulla",
onAction: () => restoreItem(itemId) // Arrow function per il ripristino
});
};
Il Test del Proiettore Leitz
Il caso del proiettore Leitz citato da Norman, dove un solo tasto gestiva troppe funzioni causando disastri è anch'esso interessante. Grazie ad esso applicherò una regola che definirei "un'azione = un controllo distinto". Eviterò di creare icone ambigue che cambiano funzione in base al contesto in modo poco chiaro; se un'azione è diversa, deve avere un controllo visivamente distinto.
La Rivelazione
Grazie a Don Norman ho capito che l'approccio al CSS non dev'essere focalizzato sulla pura decorazione, un'ombra non serve a rendere il design più moderno o semplicemente gradevole da vedere. Ho compreso che ogni pixel ha un peso comunicativo e la fisica simulata serve a dire al cervello rettiliano dell'utente "questo oggetto si può premere". Ho infatti smesso di vedere le micro-interazioni come vezzi stilistici e ho iniziato a vederle come dialoghi necessari, perché se il sistema non risponde entro 100 millisecondi con un cambio di stato, l'utente perde inevitabilmente fiducia nell'interfaccia. E, come abbiamo visto, non è mai colpa dell'utente: se lui sbaglia, significa che sono io ad aver fallito nella progettazione.
Cosa Ho Imparato
- Affordance non è bellezza: Un bottone bellissimo che non sembra un bottone è inutile, la funzione deve essere visibile nella forma.
- Feedback Asincrono: L'utente deve sempre sapere se il sistema sta lavorando, per questo i Loading States non sono opzionali.
- Reversibilità (Undo > Confirm): Permettere di rimediare all'errore è infinitamente meglio che terrorizzare l'utente con modali di conferma continue.
- Mapping Spaziale: I controlli devono essere vicini agli oggetti che modificano, raggruppati logicamente per ridurre il carico cognitivo.
Riflessione
Se dovessi riassumere il libro in una frase direi che il design invisibile è il design migliore. Se un utente nota il mio bottone perché è esteticamente gradevole, ho fatto un lavoro grafico, ma se lo clicca senza nemmeno pensarci sapendo esattamente cosa succederà, ho fatto un lavoro di ingegneria. Il mio compito non è stupire l'occhio, ma liberare la mente da carichi cognitivi inutili. Ogni volta che un utente deve fermarsi a pensare se un elemento è cliccabile, ho fallito. In altre parole il mio codice deve essere la risposta a quella domanda prima ancora che venga formulata.