# Come costruire una schermata di login accessibile

Una schermata di login accessibile ha una label per campo, un errore collegato all'input e un solo pulsante di invio. Ecco la base, il blocco pronto e il limite da colmare.

> Pagina: https://ui.eneawork.it/blog/come-costruire-una-schermata-di-login
> Markdown: https://ui.eneawork.it/blog/come-costruire-una-schermata-di-login.md

*2026-11-01*

Una schermata di login accessibile è un form con una label visibile per ogni campo, un errore collegato al campo che lo ha causato e un solo pulsante di invio. Il resto si aggiunge dopo, senza toccare quella base.

Quasi tutti i login che falliscono un test di accessibilità sbagliano nello stesso punto: il placeholder fa da label e il messaggio d'errore compare accanto al campo, ma non viene annunciato.

## Quattro elementi non negoziabili

I gestori di password leggono autoComplete: email su un campo e current-password sull'altro, mai off. Senza quegli attributi l'utente copia la password a mano, e su mobile quello è il punto in cui chiude la pagina.

## Cosa c'è nel catalogo

Il blocco login-01 copre email, password e link; login-04 aggiunge l'immagine di copertina. Sfoglia la categoria login per le varianti. Sorgente: github.com/shadcn-ui/ui, licenza MIT.

Il 1 novembre 2026 abbiamo letto da /api/stats: 111.985 componenti, 1.556 blocchi, 12.808 icone e 1.472 librerie. Lo stesso giorno docs/keyword-data.json, raccolto il 31 ottobre 2026 con la Stack Exchange API, riporta 556 voti sulla domanda «What is aria-label and how should I use it?» e 8.264 domande sul tag accessibility: il nome accessibile resta il dubbio più frequente. Un secondo comando, eseguito lo stesso giorno su data/blocks.json, conta 15 blocchi con la categoria login su 1.556, da 3 fonti su 28: nove di ephraimduncan, cinque di shadcn/ui, uno di preblocks.

## Il form minimo che passa

npx shadcn@latest add input label button

import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; export function LoginForm({ error }: { error?: string }) { return ( Email Accedi {error ? ( {error} ) : null} ); }

## L'esempio concreto: focus dopo l'errore

Il form qui sopra copre i campi e collega il messaggio. Manca il passo dopo la risposta del server: il focus resta sul pulsante e chi usa la tastiera non sa che è successo qualcosa. Un ref sul paragrafo e un focus() dentro useEffect chiudono il cerchio: la live region annuncia, il focus porta.

import { useEffect, useRef } from "react"; export function LoginError({ message }: { message: string }) { const ref = useRef(null); useEffect(() => { ref.current?.focus(); }, [message]); return ( {message} ); }

Il tabIndex={-1} rende il paragrafo raggiungibile dal codice senza aggiungerlo all'ordine di tabulazione. Senza quell'attributo focus() non fa nulla su un elemento non interattivo: il messaggio resta muto.

## Il limite da conoscere

Limite: login-01 non include una live region e non collega l'errore all'input con aria-describedby. Il blocco copre l'aspetto e la struttura; l'annuncio dell'errore lo aggiungi tu, come nello snippet qui sopra. Se usi un server che restituisce l'errore dopo il submit, metti il focus sul messaggio o sulla form, non lasciarlo sul pulsante.

## Quanti blocchi ci sono davvero

Cinque dei quindici blocchi sono di shadcn/ui, e login-01 dichiara cinque registryDependencies e due file: add login-01 scrive anche card, field, input, label e button. Contare le dipendenze prima del comando evita di scoprire un componente in più nel diff.

## La regola di decisione

Regola: se l'errore nasce dalla validazione prima dell'invio, bastano aria-describedby e aria-invalid. Se arriva dal server dopo l'invio, serve anche il focus sul messaggio. Se servono entrambi, un solo nodo con role="alert" fa il lavoro: due regioni sullo stesso testo lo annunciano due volte.

## L'errore da evitare

Disabilitare il pulsante mentre la richiesta è in corso sembra prudente e rompe il flusso: il focus cade sul body, la pagina sembra bloccata e chi usa uno screen reader perde il punto. Lascia il pulsante attivo, mostra «invio in corso» e ignora il secondo invio con un flag.

Per confrontare le varianti leggi Migliori login blocks 2026 e la checklist in 12 punti per componenti React accessibili. Se devi decidere fra blocco pronto e primitiva headless, parti da Copy-paste o headless.

## FAQ

### Il placeholder può sostituire la label?

No: il placeholder sparisce appena l'utente scrive e non ha un nome stabile per lo screen reader. Tieni una label visibile e usa il placeholder solo come esempio.

### Come collego l'errore al campo giusto?

Con aria-describedby che punta all'id del messaggio e aria-invalid sul campo. Il testo rosso da solo non dice nulla a chi non lo vede.

### Serve una live region per un solo errore?

Sì, se l'errore appare dopo il submit senza spostare il focus. role=alert o aria-live=assertive annunciano il messaggio appena compare.

### Meglio email e password in due step o in un form solo?

Un form solo, finché non hai un motivo misurato per separarli. Due step aggiungono un caricamento e un punto in cui l'utente può abbandonare.

### I blocchi di login del catalogo sono utilizzabili in produzione?

Sì, login-01 e login-04 sono MIT. Sono codice shadcn/ui da copiare, quindi la manutenzione e i test restano tuoi.
