Forme DS

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.

Approche. HTML sémantique d'abord (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 est tone (pas variant) : success (payée), info (envoyée), danger (en retard), neutral (brouillon), warning.
  • Actions de ligne = MoreMenu. Déclencheur « ⋮ », variant="ghost" par défaut. Donne un label unique par ligne (ex. « Actions — FAC-2025-0192 ») pour l'a11y. L'action destructive porte variant: "destructive", isolée par un { type: "divider" }.
  • Colonne d'actions. Étroite, alignée à droite, sans en-tête visible (aria-label sur le th).

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 MoreMenu a un label distinct — « 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.