React

Badge di stato: testo e colore, mai solo colore

Un badge di stato dice lo stato a parole e lo conferma con il colore: quattro varianti, un'etichetta per stato e contrasto 4.5:1 sul testo piccolo.

In sintesi

  • Il testo è l'informazione, il colore è la conferma: WCAG 2.2 chiede di non usare il solo colore per comunicare.
  • Le quattro varianti di `Badge` coprono quasi tutti gli stati se associ a ognuna un'etichetta fissa.
  • Il testo di un badge è piccolo: serve contrasto 4.5:1, non 3:1.

Risposta breve: un badge di stato porta sempre un'etichetta scritta e usa il colore come conferma. Mappa ogni stato su una variante di Badge e su un testo fisso, poi verifica il contrasto: 4.5:1, perché il testo è piccolo.

Quattro varianti, cinque stati

StatoVarianteTesto
Attivodefault«Attivo»
In attesasecondary«In attesa»
Fallitodestructive«Fallito»
Bozzaoutline«Bozza»
Archiviatooutline con testo attenuato«Archiviato»

La mappa vive in un oggetto tipizzato, non in una catena di if. Così uno stato nuovo non compila finché non ha la sua etichetta, e il testo non può restare indietro rispetto al colore.

import { Badge } from "@/components/ui/badge";

const tones = {
  active: { variant: "default", label: "Attivo" },
  pending: { variant: "secondary", label: "In attesa" },
  failed: { variant: "destructive", label: "Fallito" },
  draft: { variant: "outline", label: "Bozza" },
} as const;

export type State = keyof typeof tones;

export function StatusBadge({ state }: { state: State }) {
  const tone = tones[state];
  return {tone.label};
}

Il colore di un quinto stato

Un avviso giallo non è una variante di Badge: è un token nuovo. Si aggiunge con i tre passaggi del tema, valore chiaro, valore scuro e mappatura in @theme inline, poi si crea una variante locale nel file del componente. Il procedimento è in come tematizzare shadcn.

Forma, dimensione e annunci

  • Punto più testo per le liste compatte: il punto è decorativo, quindi aria-hidden.
  • Niente badge di sola icona: un cerchio verde non dice «attivo» a chi non vede il verde.
  • Per uno stato che cambia da solo usa role="status" sul contenitore, non sul badge, e non annunciare a ogni render.

Fonte: il criterio WCAG 2.2 1.4.1 vieta di usare il solo colore per trasmettere informazione, e il criterio 1.4.3 chiede 4.5:1 per il testo normale, che è la taglia di un badge; il componente badge.tsx di shadcn/ui è distribuito con licenza MIT.

Limite dichiarato

La variante destructive è pensata per le azioni, non per gli stati: usata su un badge «Fallito» funziona, ma su una tabella di dieci righe fallite diventa rumore e perde il segnale. Secondo limite: il badge non è una live region, quindi uno stato che cambia dopo un fetch non viene annunciato da un lettore di schermo finché non aggiungi role="status" al contenitore giusto.

Dove continuare

Nel catalogo trovi la libreria shadcn, un blocco badge con anteprima e i blocchi della categoria dashboard. Il metodo per misurare le coppie di colore è in palette UI e contrasto misurato, i token in come tematizzare shadcn; il confronto tra temi pronti è temi shadcn gratis o premium. Per il badge dentro una tabella lunga leggi le migliori librerie di tabella React; per uno stato scritto invece che colorato, accordion per le FAQ.

Domande frequenti

Posso usare solo il pallino colorato senza testo?

No. Il criterio WCAG 2.2 1.4.1 chiede che il colore non sia l'unico veicolo dell'informazione: serve un'etichetta scritta, oppure un `aria-label` che la sostituisca.

Quale contrasto serve per il testo del badge?

4.5:1, perché il testo di un badge è piccolo: la soglia 3:1 vale solo per testo grande, da 18.66px in grassetto o 24px normale.

Come aggiungo uno stato di avviso giallo?

Definisci `--warning` e `--warning-foreground` in `:root` e `.dark`, mappali in `@theme inline`, poi aggiungi una variante locale a `badge.tsx`. Senza la mappatura la utility non esiste.

Il badge deve essere un link o un pulsante?

Nessuno dei due: è un'etichetta. Se deve filtrare la tabella, metti il filtro su un `Button` accanto e lascia il badge come testo, altrimenti l'utente non sa cosa è cliccabile.

Il cambio di stato viene annunciato dai lettori di schermo?

Non dal badge. Aggiungi `role="status"` al contenitore che cambia e lascia che il testo del badge sia il contenuto annunciato, senza `aria-live` sul badge stesso.