Patterns & Shell
Tables & données
Le DS ne fournit pas de composant « DataGrid » : une table de données Forme est une
<table> HTML native stylée par les tokens — en-têtes
mono, séparateurs --color-border — augmentée de composants DS là où c'est utile :
Badge pour les statuts et MoreMenu pour les
actions de ligne.
thead/tbody/th),
style par tokens, chiffres tabulaires alignés à droite. On garde le contrôle total du markup et de la
performance ; on injecte des composants DS uniquement pour les statuts et les actions.
Aperçu — liste de factures
import { MoreMenu, Badge } from "@forme-ch/ui/controls";
<table style={tableStyle}>
<thead>
<tr>
<th style={thMono}>Référence</th>
<th style={thMono}>Client</th>
<th style={thMono}>Échéance</th>
<th style={{ ...thMono, textAlign: "right" }}>Montant (CHF)</th>
<th style={thMono}>Statut</th>
<th style={thMono} aria-label="Actions"></th>
</tr>
</thead>
<tbody>
{rows.map((r) => (
<tr key={r.ref}>
<td style={tdNum}>{r.ref}</td>
<td style={td}>{r.client}</td>
<td style={tdMuted}>{r.echeance}</td>
<td style={tdNum}>{r.montant}</td>
<td style={td}><Badge tone={r.statut.tone} label={r.statut.label} /></td>
<td style={tdRight}>
<MoreMenu label={`Actions — ${r.ref}`} items={[
{ label: "Ouvrir", onClick: () => open(r) },
{ label: "Dupliquer", onClick: () => dup(r) },
{ label: "Marquer payée", onClick: () => pay(r) },
{ type: "divider" },
{ label: "Supprimer", variant: "destructive", onClick: () => del(r) },
]} />
</td>
</tr>
))}
</tbody>
</table>
// Tokens clés :
// thMono → font mono, 12px (plancher typo), uppercase, letter-spacing .06em, color var(--color-text-secondary)
// td/tdNum → border-bottom: 1px solid var(--color-border) ; tabular-nums + text-align:right pour les montants Statuts & actions
- Statut =
Badge. La prop esttone(pasvariant) :success(payée),info(envoyée),danger(en retard),neutral(brouillon),warning. - Actions de ligne =
MoreMenu. Déclencheur « ⋮ »,variant="ghost"par défaut. Donne unlabelunique par ligne (ex. « Actions — FAC-2025-0192 ») pour l'a11y. L'action destructive portevariant: "destructive", isolée par un{ type: "divider" }. - Colonne d'actions. Étroite, alignée à droite, sans en-tête visible (
aria-labelsur leth).
Chiffres & alignement
Les colonnes de nombres sont alignées à droite avec font-variant-numeric: tabular-nums
(mêmes chiffres = mêmes largeurs → colonnes propres). Les en-têtes de ces colonnes s'alignent aussi à
droite. Les références (codes) restent alignées à gauche mais en chiffres tabulaires.
Débordement
Enveloppe la table dans un conteneur overflow-x: auto avec une largeur
minimale (min-width) : sur mobile, la table scrolle horizontalement au lieu
de casser la mise en page.
Accessibilité
- Markup sémantique :
<thead>+<th>lient chaque cellule à son en-tête pour les lecteurs d'écran. - Chaque
MoreMenua unlabeldistinct — « Actions » seul est ambigu quand la table en compte des dizaines. - Le statut passe par
Badge(texte lu), jamais par une pastille de couleur seule.
Do & Don't
✓ À faire
Table native + tokens. Badge tone pour les statuts, MoreMenu labellisé par ligne, montants en tabular-nums à droite, wrapper scrollable.
✕ À éviter
Empiler une lib de datagrid pour trois colonnes. Des <div> en fausse table. Un MoreMenu sans label unique. Colorer un statut sans texte.