Nel 2006, Tim Berners-Lee e Noah Mendelsohn pubblicarono un documento del TAG W3C chiamato «The Rule of Least Power». L'idea centrale è ingannevolmente semplice: quando si progetta una soluzione, scegliere il linguaggio o la tecnologia meno potente adatta al compito.
Non si tratta di essere primitivi. Si tratta di leva. Meno potente è lo strumento, più la piattaforma può fare per te — accessibilità, performance, resilienza e portabilità sono integrate. Nel momento in cui usi JavaScript per fare ciò che HTML già fa, ti assumi una responsabilità che il browser era pronto a gestire gratuitamente.
Dopo 16 anni di sviluppo frontend, ho scoperto che questo principio è una delle guide più costantemente preziose nel mio lavoro. È il motivo per cui resisto quando i team cercano una libreria datepicker, un componente modale o un framework di validazione form — quando il browser fornisce già tutto questo.
La scala di potenza
Pensa alle tecnologie frontend come una scala di potenza crescente — e di costo crescente:
HTML → CSS → JavaScript → Framework (React, Vue) → Libreria → Framework aggiuntivo/meta-framework.
Ogni gradino che sali aggiunge: dimensione del bundle, onere di manutenzione, perdite di astrazione, bug potenziali e cose da imparare. La regola dice: resta il più in basso possibile sulla scala. Se HTML può farlo, fermati lì. Se hai bisogno di logica di stile, CSS. Se hai bisogno di interattività, JS vanilla. Usa React solo quando hai bisogno di stato dei componenti e reattività.
La maggior parte dei team sale in cima alla scala per default e non guarda mai in basso. Installano una libreria form prima di provare un form nativo. Aggiungono un componente modale prima di scoprire <dialog>. Scrivono JavaScript per cose che CSS gestisce da anni.
Validazione form: il pattern più sovra-ingegnerizzato in React
È qui che la regola paga di più. Ho visto innumerevoli codebase React con form controllati, useState per ogni campo, funzioni di validazione personalizzate, gestione dello stato degli errori — centinaia di righe di codice che replicano ciò che il browser fa nativamente.
La validazione form HTML è incredibilmente potente: required, minLength, maxLength, pattern (regex completa), type="email", type="url", type="number" con min/max/step. Il browser valida, mostra messaggi di errore accessibili, impedisce l'invio e annuncia gli errori agli screen reader. Tutto gratis.
Combina questo con il pattern uncontrolled form di React (useRef + FormData) e ottieni il meglio di entrambi i mondi: il modello a componenti di React con la validazione nativa del browser. Zero re-render durante l'input, zero stato di validazione da gestire, zero bug dalla sincronizzazione stato/DOM.
// ✅ The "least power" way — HTML does the work
function ContactForm({ onSubmit }: { onSubmit: (data: FormData) => void }) {
const formRef = useRef<HTMLFormElement>(null);
return (
<form
ref={formRef}
onSubmit={(e) => { e.preventDefault(); onSubmit(new FormData(formRef.current!)); }}
>
<input name="name" required minLength={2} />
<input name="email" type="email" required />
<button type="submit">Send</button>
</form>
);
}
// Zero state. Zero re-renders. The browser validates for you.
// Works without JavaScript. Accessible by default.Confronta con il tipico «modo React»:
// ❌ The "React way" — 40 lines for a validated form
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [errors, setErrors] = useState<Record<string, string>>({});
const validate = () => {
const errs: Record<string, string> = {};
if (!name.trim()) errs.name = 'Required';
if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) errs.email = 'Invalid';
setErrors(errs);
return Object.keys(errs).length === 0;
};
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
if (!validate()) return;
submit({ name, email });
};
// Plus: controlled inputs, error rendering, re-renders on every keystroke…CSS può fare più di quanto pensi
Devi mostrare un messaggio di errore di validazione solo quando un campo è invalido? Non scrivere useState + render condizionale. Le pseudo-classi CSS come :invalid, :valid, :placeholder-shown e :user-invalid possono gestirlo. Combinale con il selettore fratello adiacente (+) e hai feedback di validazione senza JavaScript.
Il pattern :not(:placeholder-shown):invalid è particolarmente potente — mostra l'errore solo dopo che l'utente ha iniziato a digitare, evitando il flash di rosso al caricamento della pagina. È un requisito UX comune che i team risolvono con stato JavaScript. CSS lo fa in una riga.
/* Show validation errors with zero JavaScript */
input:not(:placeholder-shown):invalid {
border-color: #d32f2f;
}
/* Show the error message next to an invalid field */
input:not(:placeholder-shown):invalid + .error-hint {
display: block;
}
/* The HTML — that's it */
/* <input type="email" required placeholder="you@example.com" /> */
/* <span class="error-hint">Please enter a valid email</span> */
.error-hint {
display: none;
color: #d32f2f;
font-size: 0.85rem;
}L'elemento <dialog>: elimina la tua libreria modale
L'elemento HTML <dialog> ti offre: modalità modale e non-modale, un backdrop integrato (stilizzabile con ::backdrop), focus trapping automatico, Esc per chiudere, l'attributo inert sul contenuto di sfondo e un valore di ritorno tramite <form method="dialog">.
È tutto ciò che fa la tua libreria modale — tranne che è zero byte, zero dipendenze, accessibile di default e già nel browser. Ogni browser moderno lo supporta. Ho sostituito 3 diverse librerie modali con <dialog> in codebase di produzione.
<!-- ❌ npm install some-modal-library -->
<!-- Bundle size: +12KB gzipped, new API to learn, z-index wars -->
<!-- ✅ HTML <dialog> — built in, accessible, zero dependencies -->
<dialog id="confirm">
<h2>Are you sure?</h2>
<form method="dialog">
<button value="cancel">Cancel</button>
<button value="confirm">Confirm</button>
</form>
</dialog>
<script>
// Show it
document.getElementById('confirm').showModal();
// Esc to close: free. Focus trap: free. Backdrop: free.
// method="dialog" closes it and gives you the return value.
</script>requestSubmit(), pattern e input date nativi
Tre altri esempi della regola in azione:
requestSubmit() è l'API DOM che la maggior parte degli sviluppatori React non conosce. A differenza di form.submit() che bypassa la validazione, requestSubmit() attiva la validazione nativa dei vincoli del browser. Se il form è invalido, il browser mostra la sua UI di errore integrata. Se valido, lancia l'evento submit. Una riga di codice sostituisce una funzione di validazione personalizzata.
L'attributo HTML pattern accetta una regex JavaScript completa. Devi validare un formato di telefono? Un codice prodotto? Un codice postale? pattern lo fa in modo dichiarativo, con messaggi di errore accessibili. Nessuna libreria necessaria.
Gli input date HTML sono controversi perché appaiono diversi tra i browser. Ma è in realtà un vantaggio — su mobile attivano il selettore di date nativo (ruota su iOS, calendario su Android), che è di gran lunga superiore a qualsiasi datepicker JavaScript. Discuto attivamente con i team UI/UX per progettare attorno all'input nativo piuttosto che sostituirlo. L'esperienza mobile da sola lo giustifica.
<!-- HTML pattern attribute — regex validation, no JS needed -->
<input
type="text"
name="phone"
pattern="[0-9+\s]{7,15}"
title="Phone number (digits, spaces, +)"
required
/>
<!-- requestSubmit() — trigger native validation from JS -->
<script>
// Unlike form.submit(), requestSubmit() runs validation first
document.getElementById('myForm').requestSubmit();
// If invalid → shows native error tooltips
// If valid → fires the submit event
</script>
<!-- HTML date input — no datepicker library needed -->
<input type="date" name="birthday" min="1920-01-01" max="2026-01-01" />
<!-- Native on every browser. Better UX on mobile (native date wheel). -->Web Components: quando serve riusabilità senza framework
A volte serve un componente riutilizzabile che funzioni attraverso i framework — o senza. È qui che i Web Components brillano. In AXA ho costruito una libreria di componenti aziendale usando Web Components perché i team usavano framework diversi. I componenti funzionavano ovunque: app React, pagine HTML pure, template CMS.
I Web Components si posizionano perfettamente sulla scala di potenza: più potenti di HTML/CSS da soli, ma meno potenti (e meno costosi) di una libreria di componenti specifica per framework. Usano il modello nativo di componenti della piattaforma — Shadow DOM, Custom Elements, slot — piuttosto che uno virtuale del framework.
La Rule of Least Power non significa «non usare mai strumenti potenti». Significa: non usarli quando uno strumento più semplice fa il lavoro. I Web Components sono la risposta giusta per il riuso cross-framework. React è la risposta giusta per la gestione di stato complesso. Una libreria npm è la risposta giusta quando l'API nativa è davvero insufficiente. La chiave è ricorrervi deliberatamente, non per default.
Come applicarlo da domani
Prima di aggiungere una dipendenza o scrivere una soluzione personalizzata, sali la scala: HTML può farlo? (required, pattern, type, <dialog>, <details>, <datalist>). CSS può farlo? (:invalid, :has(), scroll-snap, container queries). Un'API DOM nativa può farlo? (requestSubmit, FormData, Constraint Validation API, Intersection Observer). Il JS vanilla può farlo senza libreria?
Solo quando hai risposto «no» a ogni gradino dovresti passare al livello successivo. Questo non ti rallenta — ti accelera. Meno codice da scrivere, meno da mantenere, meno bug, performance migliori e accessibilità integrata.
Il W3C ce l'ha detto vent'anni fa. Il browser diventa più potente ogni anno da allora. La maggior parte di ciò che installi via npm oggi, la piattaforma lo fornisce già. Basta guardare in basso prima di salire.

