Aller au contenu
Design system

Composants · forms

Formulaires

Tous les champs passent par Field : libellé, astérisque obligatoire, aide, erreur et liaisons ARIA identiques partout. Validation au blur ; les messages d’erreur disent comment corriger. Montants en unités mineures (bigint), jamais de flottant.

  • TextField
  • Textarea
  • NumberField
  • QuantityField
  • MoneyField
  • DateField
  • DateRangeField
  • SearchInput
  • FileUpload
  • LineItemsEditor
  • Field
  • FieldGroup
  • InputAdornments
  • FormSection
  • FormGrid
  • FormActions
  • CashCountTable

Texte

TextField

Quand l’utiliser : Saisie courte d’un texte libre : nom, référence, adresse. Ornements pour une icône ou une unité ; compteur pour les champs limités.

Numéro de bon de commande du client, repris sur la facture.

L’IFU comporte 8 chiffres suivis d’une lettre, ex. 00012345A.

Burkina Faso

26 / 60

Attribué à la confirmation, non modifiable.

Survol : la bordure se renforce. Focus : contour menthe. Erreur : bordure rouge + message relié par aria-describedby. Lecture seule : fond atténué, reste sélectionnable.
Afficher le code
<TextField label="Raison sociale" required placeholder="Ex. Grossiste Démo Kossodo" />
<TextField label="Référence client" hint="Numéro de bon de commande du client" />
<TextField label="IFU" required error="L’IFU comporte 8 chiffres suivis d’une lettre, ex. 00012345A." />
<TextField label="E-mail" leading={<AtSign />} type="email" />
<TextField label="Libellé" maxLength={60} showCount />
<TextField label="Code tiers" disabled value="CLI-00042" />
<TextField label="N° de commande" readOnly value="CMD-2026-00421" />

Textarea

Quand l’utiliser : Texte long : instructions de livraison, commentaire, motif. autosize pour suivre le contenu jusqu’à maxRows.

103 / 500

Le commentaire doit faire au moins 10 caractères pour être utile.

Afficher le code
<Textarea label="Instructions de livraison" autosize maxRows={6} maxLength={500} showCount />

SearchInput

Quand l’utiliser : Recherche dans une liste ou un tableau : appel temporisé (300 ms), immédiat sur Entrée, bouton d’effacement, indication de raccourci.

Tapez pour lancer une recherche simulée.

Afficher le code
<SearchInput
  value={query} onValueChange={setQuery}
  onSearch={(q) => fetchOrders(q)} loading={loading}
  placeholder="N° de commande, client…" shortcutHint="/"
/>

Nombres, quantités et montants

NumberField et QuantityField

Quand l’utiliser : NumberField pour une valeur décimale (taux, rendement, humidité) ; QuantityField pour une quantité avec son unité, éventuellement sélectionnable (unité de stock / d’achat). La valeur est une chaîne décimale canonique : aucune perte de précision.

%

Objectif site : 62 %.

%

Au-delà de 14 %, le lot part au séchage.

sac
kg

Calculé après la seconde pesée.

Flèches haut/bas : incrément de step. Séparateurs de milliers affichés hors saisie. Bornes min/max et nombre de décimales vérifiés au blur avec un message correctif.
Afficher le code
const [value, setValue] = useState('63.4');
<NumberField label="Rendement usinage" value={value} onValueChange={setValue} unit="%" min={0} max={100} maxDecimals={1} />

<QuantityField
  label="Quantité réceptionnée" value={qty} onValueChange={setQty}
  unit={unit} units={[{ value: 'kg', label: 'kg' }, { value: 't', label: 't' }]} onUnitChange={setUnit}
/>

MoneyField

Quand l’utiliser : Toute saisie de montant. Valeur en unités mineures (bigint) : francs pour le XOF (sans décimales), centimes pour l’EUR. Le montant en toutes lettres sécurise les gros montants (chèques, règlements).

F CFA

dix-huit millions sept cent cinquante mille francs CFA

Modifiez le montant : la version en lettres suit.

F CFA

moins un million cent vingt-cinq mille francs CFA

F CFA

Saisissez l’acompte : 30 % minimum pour un nouveau client, soit 5 625 000 F CFA.

EUR
F CFA

Modifiable par le contrôle de gestion.

Saisie libre (« 18750000 », « 18 750 000 ») ; formatage au blur ; négatif refusé sauf allowNegative (avoirs), affiché en rouge avec le signe −.
Afficher le code
const [amount, setAmount] = useState<bigint | null>(18750000n);
<MoneyField label="Montant du règlement" currency="XOF" value={amount} onValueChange={setAmount} showAmountInWords required />
<MoneyField label="Montant de l’avoir" currency="XOF" value={credit} onValueChange={setCredit} allowNegative />
<MoneyField label="Acompte" currency="XOF" value={null} onValueChange={…} error="…" />
<MoneyField label="Montant en euros" currency="EUR" value={1250050n} onValueChange={…} />

Dates

DateField et DateRangeField

Quand l’utiliser : DateField pour une date unique (livraison, échéance) ; DateRangeField pour une période (filtre de liste, rapport). Valeurs ISO « AAAA-MM-JJ », affichage local jj/mm/aaaa.

Au plus tôt demain.

Calculée : 30 jours fin de mois.

Période d’analyse
Période (incohérente)

La date de fin doit être postérieure ou égale à la date de début.

DateRangeField vérifie la cohérence : la fin ne peut pas précéder le début (message correctif automatique).
Afficher le code
<DateField label="Date de livraison souhaitée" value={date} onChange={(e) => setDate(e.target.value)} min="2026-10-13" />
<DateRangeField label="Période" value={range} onValueChange={setRange} />

Fichiers

FileUpload

Quand l’utiliser : Pièces jointes d’un document : glisser-déposer ou bouton, validation (format, taille, nombre) et suivi complet : envoi, analyse antivirus, prêt, refusé, infecté.

Glissez-déposez vos fichiers ici, ou

  • bon-de-commande-client-BC7781.pdf242,5 Ko
    Prêt
  • certificat-qualite-lot-RB25.pdf1,4 Mo
    Analyse antivirus…
  • photo-camion-chargement.jpg3,1 Mo
    Envoi… 62 %
  • releve-bancaire-octobre.xlsx14,1 Mo
    releve-bancaire-octobre.xlsx dépasse 10 Mo (14,8 Mo) : compressez-le ou exportez la période utile.
  • facture-proforma.pdf.exe93,8 Ko
    Menace détectée par l’antivirus : le fichier a été supprimé.

Formats : PDF, images, XLSX — 10 Mo max. par fichier

Glissez-déposez vos fichiers ici, ou

Formats : PDF

Ajoutez un fichier pour voir la séquence simulée en attente → envoi → analyse → prêt. Les fichiers refusés restent listés avec leur motif.
Afficher le code
<FileUpload
  label="Pièces jointes" files={files}
  onFilesAdded={(items) => { setFiles((f) => [...f, ...items]); items.filter((i) => i.status === 'pending').forEach(upload); }}
  onRemove={(item) => setFiles((f) => f.filter((x) => x.id !== item.id))}
  accept=".pdf,image/*,.xlsx" maxSize={10 * 1024 * 1024} maxFiles={8}
/>

Lignes de document

LineItemsEditor : grille de saisie au clavier (Entrée sur la dernière ligne = nouvelle ligne, flèches haut/bas entre lignes), totaux en pied.

Commande CMD-2026-00421 — 3 lignes

Quand l’utiliser : Saisie des lignes d’un document commercial ou logistique (devis, commande, réception, écriture). Le parent possède les lignes et calcule les totaux ; chaque cellule reçoit un libellé accessible « Quantité, ligne 2 ».

Lignes de la commande CMD-2026-00421
N°ArticleQuantitéPrix unitaireMontant HTActions
1
sac
F CFA
12 500 000 F CFA
2
sac
F CFA
5 000 000 F CFA
3
sac
F CFA
1 250 000 F CFA
18 750 000 F CFA
Total HT
18 750 000 F CFA
TVA 18 %
3 375 000 F CFA
Total TTC
22 125 000 F CFA
Le prix se remplit depuis la grille tarifaire au choix de l’article. Montants calculés en bigint (quantité décimale × prix en francs), TVA 18 %.
Afficher le code
const columns: LineItemColumn<OrderLine>[] = [
  { id: 'item', header: 'Article', width: '40%',
    render: (row, ctx) => <Combobox label={ctx.label} hideLabel size="sm" options={items} value={row.itemCode}
                                    onValueChange={(v) => ctx.update({ itemCode: v, unitPrice: priceList[v] })} /> },
  { id: 'qty', header: 'Quantité', align: 'end',
    render: (row, ctx) => <QuantityField label={ctx.label} hideLabel size="sm" value={row.quantity}
                                         onValueChange={(quantity) => ctx.update({ quantity })} unit="sac" /> },
  { id: 'amount', header: 'Montant HT', align: 'end',
    render: (row) => <MoneyText value={lineAmount(row)} currency="XOF" />, footer: <MoneyText value={totalHt} currency="XOF" /> },
];

<LineItemsEditor
  caption="Lignes de la commande" columns={columns} rows={rows} getRowId={(r) => r.id} showLineNumbers
  onChange={(i, row) => setRows((rs) => rs.map((r, j) => (j === i ? row : r)))}
  onAdd={() => setRows((rs) => [...rs, emptyLine()])}
  onRemove={(i) => setRows((rs) => rs.filter((_, j) => j !== i))}
  footer={<Totals ht={totalHt} vat={vat} ttc={totalTtc} />}
/>

Structure de formulaire

FormSection, FormGrid et FormActions

Quand l’utiliser : Structurer un formulaire de fiche : sections encadrées (card) ou simples (plain), grille responsive 1 → 2 ou 3 colonnes, barre d’actions avec une seule action principale à droite (collante en bas en usage réel).

Identité du client

Informations légales reprises sur les factures.

Conditions commerciales

F CFA
jours
%
Afficher le code
<FormSection title="Identité du client" description="…" actions={<Button variant="link">Importer depuis l’IFU</Button>}>
  <FormGrid columns={2}>
    <TextField label="Raison sociale" required />
    <TextField label="Adresse" fieldClassName="md:col-span-full" />
  </FormGrid>
</FormSection>
<FormActions sticky={false}
  secondary={<Button>Annuler</Button>}
  primary={<Button variant="primary" type="submit">Créer le client</Button>} />

Field, FieldGroup et InputAdornments

Quand l’utiliser : Pour construire un champ spécifique qui n’existe pas encore dans le design system, sans jamais recréer un libellé à la main : Field fournit id, aria-describedby et l’état invalide ; FieldGroup regroupe plusieurs contrôles sous une légende ; InputAdornments superpose icône ou unité.

8 chiffres suivis d’une lettre.

Téléphone du livreur

Indicatif pays et numéro.

lots
Afficher le code
<Field label="Code IFU" hint="8 chiffres + 1 lettre" required>
  {(f) => <input id={f.id} aria-describedby={f.describedBy} aria-invalid={f.invalid || undefined}
                 className={cn(inputBaseClasses, inputSizeClasses.md, 'font-mono uppercase')} />}
</Field>

<FieldGroup label="Téléphone du livreur" hint="Indicatif et numéro">
  {(g) => <div className="flex gap-2">…</div>}
</FieldGroup>

<InputAdornments leading={<Search />} trailing="lots">
  {(style) => <input style={style} className={cn(inputBaseClasses, inputSizeClasses.md)} />}
</InputAdornments>

Billetage

CashCountTable — billetage

Quand l’utiliser : Arrêté de caisse, ouverture de session, remise d’espèces : quantités par coupure (billets et pièces XOF par défaut), sous-totaux, total compté et écart avec le solde théorique.

Billetage — saisie
CoupureQuantitéMontant
Billets380 000 F CFA
10 000 F CFA
300 000 F CFA
5 000 F CFA
60 000 F CFA
2 000 F CFA
20 000 F CFA
1 000 F CFA
0 F CFA
500 F CFA
0 F CFA
Pièces2 000 F CFA
500 F CFA
2 000 F CFA
250 F CFA
0 F CFA
200 F CFA
0 F CFA
100 F CFA
0 F CFA
50 F CFA
0 F CFA
25 F CFA
0 F CFA
10 F CFA
0 F CFA
5 F CFA
0 F CFA
Total compté382 000 F CFA
Montant attendu385 000 F CFA
Écart−3 000 F CFA · Manquant
Billetage — lecture seule
CoupureQuantitéMontant
Billets145 000 F CFA
10 000 F CFA12120 000 F CFA
5 000 F CFA00 F CFA
2 000 F CFA00 F CFA
1 000 F CFA2525 000 F CFA
500 F CFA00 F CFA
Pièces4 200 F CFA
500 F CFA00 F CFA
250 F CFA00 F CFA
200 F CFA00 F CFA
100 F CFA404 000 F CFA
50 F CFA00 F CFA
25 F CFA8200 F CFA
10 F CFA00 F CFA
5 F CFA00 F CFA
Total compté149 200 F CFA
Montant attendu150 000 F CFA
Écart−800 F CFA · Manquant

Dernier total saisi : —

Une pièce et un billet de 500 F ont des clés distinctes (note-500 / coin-500). Totaux en bigint via cashCountTotal. readOnly pour une fiche ou une impression ; la taille « touch » sur les terminaux.
Afficher le code
<CashCountTable expected={385_000n} defaultQuantities={{ 'note-10000': 30, 'note-5000': 12 }}
  onQuantitiesChange={(quantities, total) => setTotal(total)} />
<CashCountTable readOnly quantities={quantities} caption="Billetage de l’arrêté ARC-2026-0327" />

Grilles et champs non contrôlés

FormGrid columns={4}

Quand l’utiliser : Formulaires denses de paramétrage ou de critères (codes, unités, seuils courts) : 1 colonne sur mobile, 2 dès md, 4 dès xl. Réserver aux champs courts.

sacs
jours
Afficher le code
<FormGrid columns={4}>
  <TextField label="Code article" />
  <SelectField label="Unité de stock" options={…} />
  …
</FormGrid>

Champs non contrôlés (defaultValue)

Quand l’utiliser : Maquettes, filtres et formulaires simples où le parent n’a pas besoin de piloter la valeur : defaultValue initialise le champ, qui garde son état ; onValueChange reste facultatif.

F CFA
%
Priorité
RadioGroup, NumberField, SelectField et MoneyField acceptent value (contrôlé) ou defaultValue (non contrôlé). DateField notifie la date ISO par onValueChange.
Afficher le code
<SelectField label="Mode de règlement" defaultValue="momo" options={…} />
<MoneyField label="Montant" currency="XOF" defaultValue="358160" />
<NumberField label="Humidité" unit="%" defaultValue="13.5" maxDecimals={1} />
<RadioGroup label="Priorité" defaultValue="normal" options={…} />
<DateField label="Échéance" defaultValue="2026-10-31" onValueChange={(iso) => …} />