Patterns & Shell
États vides & toasts
Une interface passe le plus clair de son temps dans des états intermédiaires : rien à afficher, données en cours de chargement, retour après une action. Trois patterns tokenisés couvrent l'essentiel.
Aperçu interactif
Bascule entre l'état vide (message + action de sortie), le chargement (skeleton
--color-skeleton) et l'état chargé. Le bouton « Enregistrer » déclenche un
toast de confirmation.
État vide
Un état vide n'est pas une erreur : c'est une invitation. Toujours message + action de sortie.
Icône discrète, titre court, une phrase d'explication, puis un Button primaire
qui crée la première donnée.
import { Button } from "@forme-ch/ui/controls";
import { InboxIcon, PlusIcon } from "@forme-ch/ui/icons";
<div style={{ display: "grid", gap: 10, placeItems: "center", textAlign: "center",
color: "var(--color-text-secondary)", padding: 32 }}>
<InboxIcon size={32} />
<strong style={{ color: "var(--color-text-primary)" }}>Aucune facture</strong>
<p>Crée ta première facture pour la voir apparaître ici.</p>
<Button label="Nouvelle facture" variant="primary" icon={<PlusIcon />} onClick={create} />
</div> Chargement — skeleton
Pendant un fetch, réserve la place avec un skeleton au lieu d'un spinner centré : la
mise en page ne saute pas à l'arrivée des données. Blocs en
--color-skeleton, coins arrondis, légère pulsation. Le conteneur porte
aria-busy="true" et aria-live="polite".
const skel = (w) => ({
height: 12, width: w, borderRadius: 6,
background: "var(--color-skeleton)",
animation: "pulse 1.4s ease-in-out infinite",
});
<div aria-busy="true" aria-live="polite" style={{ display: "grid", gap: 16 }}>
{[0, 1, 2].map((i) => (
<div key={i} style={{ display: "grid", gap: 8 }}>
<div style={skel("60%")} />
<div style={skel("35%")} />
</div>
))}
</div>
// @keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .45 } } Fais correspondre la forme du skeleton à celle du contenu réel (nombre de lignes, largeurs) : la transition doit être imperceptible.
Toasts & retours
Après une action réussie, confirme brièvement puis efface. Un toast est transitoire
(role="status", disparition auto ~2–3 s), non bloquant, avec une icône
CheckCircleIcon teintée --color-success. Pour une
erreur, garde le retour visible jusqu'à résolution (ne pas auto-effacer un échec).
import { CheckCircleIcon } from "@forme-ch/ui/icons";
<div role="status" style={{
display: "inline-flex", alignItems: "center", gap: 8, padding: "8px 14px",
borderRadius: "var(--radius-inner)", background: "var(--color-background-surface)",
border: "1px solid var(--color-border)", boxShadow: "0 6px 20px var(--color-shadow)",
}}>
<CheckCircleIcon size={16} color="var(--color-success)" />
Modifications enregistrées
</div> Accessibilité
- Chargement :
aria-busy+aria-live="polite"pour annoncer l'arrivée du contenu sans le lire caractère par caractère. - Toast succès :
role="status"(poli). Toast erreur / alerte :role="alert"(interrompt) et pas d'auto-disparition. - Couleur + texte : le succès n'est jamais porté par la seule couleur verte — l'icône et le libellé le disent aussi.
- Les skeletons sont décoratifs : ne les rends pas focusables, laisse
aria-liveannoncer le résultat.
Do & Don't
✓ À faire
État vide = message + action de sortie. Skeleton à la forme du contenu. Toast succès poli et transitoire ; erreur persistante en role="alert".
✕ À éviter
Une page blanche sans explication. Un spinner plein écran qui fait sauter la mise en page. Un toast d'erreur qui s'efface avant lecture. Le vert seul pour dire « ok ».