Coller un tableau dans une pull request : ce qui casse, et les deux caractères qui cassent tout
Publié le 29/07/2026 · 15 min de lecture · Outils pour développeurs
Daniel Okonkwo — Développeur front-end et rédacteur Tech chez OneKitly
Performance web · Formats de fichiers
Vérifié à partir de 4 sources
Une cellule de tableau Markdown accepte tout sauf deux caractères. La barre verticale ferme la cellule : il faut donc l'écrire \| — le convertisseur le fait pour toi, colle USB-C | 2 m et il produit USB-C \| 2 m, que GitHub affiche comme une seule cellule. Le saut de ligne ferme la ligne, et il n'existe aucun échappement pour lui : une cellule contenant un vrai retour à la ligne est impossible dans la syntaxe des tableaux GFM, alors l'outil le remplace par la balise HTML <br>, la seule chose qui fonctionne. Sous l'en-tête vient la ligne de séparation, et elle est obligatoire — une ligne d'en-tête sans rangée de tirets en dessous n'est pas un tableau, et GitHub l'affiche comme un paragraphe contenant des barres. Cette rangée porte aussi l'alignement : --- laisse la valeur par défaut du moteur, :-- aligne à gauche, --: à droite, :-: centre, et un deux-points ailleurs ne fait rien. La ligne de séparation doit compter exactement autant de cellules que l'en-tête, sinon le tableau n'est pas reconnu du tout. Les espaces qui font joliment tomber les colonnes sont purement cosmétiques : | a | b | et |a|b| produisent un HTML identique octet pour octet, d'où l'interrupteur « Aligner les colonnes » et le fait qu'on peut le couper sans risque. Un détail sur l'échappement, parce qu'ici la sortie correcte a l'air fausse : l'antislash est échappé avant la barre. Une cellule qui contient déjà x \| y ressort en x \\\| y, où \\ est un antislash littéral et \| une barre littérale, si bien que GitHub affiche l'unique cellule x \| y. N'échapper que la barre donnerait x \\| y — un antislash littéral suivi d'une vraie coupure de cellule — et la colonne se scinderait. Tout est dans l'ordre, et c'est pourquoi un tableau Markdown existant peut repasser dans le convertisseur CSV et en ressortir intact.
Un tableau Markdown n'interdit que deux caractères dans une cellule : la barre verticale et le saut de ligne. Voici ce que fait chacun, comment un convertisseur les traite, pourquoi l'échappement doit être appliqué dans le bon ordre, et pourquoi l'alignement du source ne compte jamais.
Deux caractères, et deux seulement
Presque tout traverse une cellule de tableau Markdown sans dommage. Accents, idéogrammes, emoji, accents graves, astérisques, crochets, symboles monétaires, guillemets : rien de tout cela ne signifie quoi que ce soit pour l'analyseur de tableau, qui ne cherche jamais que deux choses. La barre verticale termine une cellule. Le saut de ligne termine une rangée. Voilà toute la grammaire, et chaque ennui que tu auras en collant un tableau dans une pull request sera l'un de ces deux caractères arrivé là où l'analyseur ne l'attendait pas.
La barre verticale a un échappement. Écris \| et l'analyseur lit une barre littérale au lieu d'une frontière de colonne. Le convertisseur l'applique pour toi : ses propres données d'exemple contiennent la cellule USB-C | 2 m, et faire passer l'exemple donne la ligne | Câble | USB-C \| 2 m | 9.90 |, soit une rangée de trois cellules et non de quatre. Donne-lui une cellule qui n'est qu'une barre et tu obtiens \| tout seul, toujours une cellule. Cette partie est solide, et c'est celle que les tableaux écrits à la main ratent le plus souvent : une commande shell dans un tableau de documentation — ps | grep node — gagne silencieusement une colonne et décale toutes les valeurs d'un cran vers la droite, un défaut qu'un relecteur survole parce que le tableau ressemble toujours à un tableau.
Le saut de ligne n'a aucun échappement, et c'est la partie qu'il faut intérioriser : une cellule contenant un retour à la ligne n'est pas seulement malcommode dans la syntaxe des tableaux GFM, elle est impossible. La rangée se termine au retour à la ligne, point. Il n'existe exactement que deux issues honnêtes. Remplacer la coupure par la balise HTML <br>, que GitHub autorise dans une cellule et que le convertisseur applique — un champ CSV entre guillemets valant « ligne un\nligne deux » ressort en ligne un<br>ligne deux dans une seule cellule. Ou accepter que le contenu n'a rien à faire dans un tableau et le mettre dans une liste ou un paragraphe en dessous. Tout le reste est un vœu pieux : il n'y a ni astuce d'antislash, ni barre doublée, ni marqueur de continuation.
La ligne de séparation n'est pas un ornement
GitHub Flavored Markdown appelle la rangée de tirets sous l'en-tête la ligne de séparation, et la spécification est sans ambiguïté : elle est faite de cellules dont le seul contenu est des tirets, avec éventuellement un deux-points au début, à la fin, ou aux deux. Sans elle, pas de tableau. Une ligne d'en-tête et trois lignes de données sans tirets entre elles s'affichent comme quatre paragraphes de texte contenant des barres — c'est pourquoi un tableau qui semblait bon dans un éditeur et qui a cassé dans la pull request a presque toujours perdu cette ligne-là dans un copier-coller.
Quatre formes de cellule de séparation signifient quatre choses différentes, et le convertisseur propose les quatre comme réglage d'alignement. Un --- nu laisse l'alignement au moteur, ce qui en pratique veut dire à gauche dans tous les moteurs que quiconque utilise. Un deux-points au début, :---, force à gauche. Un deux-points à la fin, ---:, force à droite, et c'est celui qu'il te faut sur toute colonne de nombres. Un deux-points aux deux bouts, :---:, centre. Un deux-points au milieu des tirets n'est pas un alignement et n'est pas valide — la cellule doit être deux-points, tirets, deux-points, dans cet ordre, et rien d'autre. Règle l'outil sur droite et un tableau à deux colonnes ressort avec | -----: | ---: | en dessous, et chaque nombre de la colonne s'aligne sur son dernier chiffre dans le rendu.
Une règle attrape l'essentiel des échecs silencieux : la ligne de séparation doit compter exactement autant de cellules que la ligne d'en-tête, sinon le tableau n'est pas reconnu du tout. La spécification le dit noir sur blanc, et le mode de défaillance est le pire qui soit — tu n'obtiens pas un tableau cassé, tu n'obtiens pas de tableau. Un en-tête de quatre colonnes au-dessus d'une séparation de trois s'affiche en texte littéral, barres comprises. C'est précisément ce qui arrive quand on ajoute une colonne à la main en oubliant les tirets. Le convertisseur ne peut pas commettre cette erreur, puisqu'il construit les deux rangées à partir du même nombre de colonnes : un argument correct en faveur de la génération plutôt que de l'édition.
L'alignement du source est pour toi, pas pour GitHub
Le convertisseur a un interrupteur « Aligner les colonnes », activé par défaut, et il ne change rien au tableau rendu. Activé, un tableau à deux colonnes ressort en | Item | Qty |, | ------ | --- |, | Widget | 3 |, cellules bien carrées. Désactivé, tu obtiens | Item | Qty |, | --- | --- |, | Widget | 3 |, en dents de scie. GitHub produit un HTML identique dans les deux cas. Les espaces existent pour qu'un humain lisant le diff voie les colonnes, et pour aucune autre raison. Coupe l'alignement quand le tableau est large et que c'est le diff qu'on lira ; laisse-le quand le fichier est édité à la main.
Il y a un piège à connaître si tes données ne sont pas latines. L'alignement est calculé en comptant les caractères, et un caractère n'est pas une colonne. Donne au convertisseur une cellule contenant deux emoji : il en compte deux, aligne sur deux, et le source ne tombe plus juste dans un éditeur où chaque emoji occupe deux colonnes de chasse fixe. La même chose se produit avec du texte chinois, japonais ou coréen, et en sens inverse avec un é écrit e plus accent combinant, soit deux caractères dans une seule colonne. Rien de tout cela n'affecte le tableau rendu : c'est un problème de lisibilité, pas de correction — mais si tu produises un large tableau de noms de lieux japonais, n'attends pas un source bien peigné.
Échapper l'échappement, et deviner le délimiteur
Un convertisseur qui remplace chaque barre par \| et s'arrête là se trompe sur exactement une entrée, et c'est celle qu'on rencontre dès qu'on réimporte un tableau qu'on a écrit soi-même : une cellule contenant déjà \|. N'échappe que la barre et x \| y devient x \\| y, où l'antislash doublé est un antislash littéral et où la barre derrière lui redevient vivante — la colonne se scinde et rien ne prévient. L'ordre doit être inverse. Ce convertisseur échappe d'abord l'antislash, puis la barre, si bien que x \| y ressort en x \\\| y, ce qui a l'air d'un antislash de trop et n'en est pas un : GFM lit \\ comme un antislash littéral et \| comme une barre littérale, et affiche l'unique cellule x \| y.
La conséquence, c'est que l'aller-retour est sûr. Convertis des données en tableau Markdown, ressors le tableau, enregistre-le en CSV, repasse-le dans l'outil : les colonnes qui contenaient des barres reviennent telles qu'elles sont parties, et tout le reste avec. Ça vaut plus que ça n'en a l'air, parce qu'exporter un tableau rendu vers un tableur puis le réimporter est une chose banale dès qu'une colonne change de nom. L'outil frère de ce site, le générateur de tableaux Markdown, referme la même boucle par l'autre bout : son analyseur lit \| comme une barre littérale quand le délimiteur est la barre et le déséchappe à l'entrée, si bien qu'un tableau collé là revient identique lui aussi.
Le délimiteur est deviné plutôt que déclaré, et la devinette se fait sur plusieurs enregistrements, pas sur le seul en-tête. L'outil compte les virgules, points-virgules, tabulations et barres hors guillemets sur cinq enregistrements au plus, et un délimiteur qui donne le même compte sur chaque enregistrement lu l'emporte sur celui qui est simplement le plus nombreux sur le premier. C'est ce qui tranche un en-tête comme A|B|C,D : seul, il ressemble à trois colonnes séparées par des barres, mais mets sous lui des lignes en virgules et la virgule gagne, parce que c'est son compte qui se répète. Un export français dont l'en-tête est Nom;Prénom est détecté en point-virgule dans les deux cas. La seule chose que le premier enregistrement décide encore seul, c'est quels délimiteurs sont candidats — un en-tête qui n'en contient aucun des quatre ne laisse rien à départager, et la devinette retombe sur la virgule. Si ta première ligne est inhabituelle, règle le délimiteur à la main.
Un enchaînement qui survit à la relecture
Exporte les données en CSV plutôt que de copier des cellules depuis un tableur : les règles de guillemets d'un fichier CSV sont la seule chose qui indique au convertisseur où s'arrête un champ contenant une virgule ou un retour à la ligne. Colle, vérifie le séparateur détecté, choisis l'alignement — à droite pour les nombres, par défaut pour le reste — et lis les deux premières lignes de sortie avant de copier. Ces deux lignes sont l'en-tête et la séparation, et si elles n'ont pas le même nombre de barres, rien ne s'affichera en aval.
Cherche ensuite les trois cellules qui posent problème. Tout ce qui contient une barre : vérifie qu'elle est sortie en \| et non en frontière de colonne vivante, et souviens-toi qu'une cellule contenant déjà un antislash le voit doubler — \\\| est la bonne sortie, pas un échappement en trop. Tout ce qui était multiligne : vérifie que c'est devenu <br> et demande-toi si c'est vraiment ce que tu veux dans un tableau. Tout ce qui est vide : une cellule vide est parfaitement légale et s'affiche vide, donc une rangée de blancs au milieu de ton tableau est une donnée, pas un dégât. Si le tableau part dans un dépôt plutôt que dans un commentaire, commite-le une fois avec l'alignement activé pour que le premier relecteur lise le diff, puis ne le reformate plus jamais — un changement d'espaces sur un tableau, c'est trente lignes de bruit dans une pull request qui ne dit rien.
| Contenu de la cellule | Ce que l'outil produit | Résultat |
|---|---|---|
| USB-C | 2 m | USB-C \| 2 m | Correct — une cellule contenant une barre visible |
| Un champ entre guillemets contenant un vrai retour à la ligne | ligne un<br>ligne deux | Correct — la seule chose que permettent les tableaux GFM ; un vrai saut de ligne est impossible |
| x \| y (déjà échappé) | x \\\| y | Correct — l'antislash est échappé d'abord, GitHub affiche l'unique cellule x \| y |
| Un champ vide | Rien entre les barres | Correct — une cellule vide est légale et s'affiche vide |
| Une rangée avec moins de champs que l'en-tête | Complétée par des cellules vides jusqu'à la rangée la plus large | Correct — un tableau irrégulier ne s'afficherait pas, l'outil le rend rectangulaire |
| Deux emoji | Alignée comme si elle faisait deux colonnes de large | Cosmétique seulement — ils en occupent quatre ; le tableau rendu n'en souffre pas |
Questions fréquentes
- Peut-on mettre un saut de ligne dans une cellule de tableau Markdown ?
- Pas un vrai. Le saut de ligne est ce qui termine une rangée : la syntaxe des tableaux n'a donc aucun moyen d'exprimer une cellule qui en contient un — il n'existe pas de séquence d'échappement comme \| pour la barre. Le contournement accepté par GitHub est la balise HTML <br>, ce que ce convertisseur substitue : un champ CSV entre guillemets contenant une coupure devient une cellule valant ligne un<br>ligne deux. Deux limites honnêtes. Les moteurs qui filtrent le HTML afficheront la balise en texte littéral, et une cellule contenant trois ou quatre <br> signale en général que le contenu veut être une liste sous le tableau plutôt qu'une cellule dedans.
- Mon tableau s'affiche en texte brut avec des barres. Qu'ai-je cassé ?
- Presque toujours la ligne de séparation. Soit elle manque, soit elle n'a pas le même nombre de cellules que l'en-tête — la spécification exige que l'en-tête et la séparation aient le même nombre de cellules, faute de quoi le tableau n'est pas reconnu et retombe en paragraphe. Compte les barres sur la ligne un et la ligne deux de ton tableau ; elles doivent être égales. L'autre cause fréquente est une ligne vide entre l'en-tête et la séparation, qui termine le bloc avant qu'il commence. Une troisième, plus rare : les cellules de séparation ne doivent contenir que des tirets et éventuellement des deux-points aux bords, donc un espace-tiret-espace égaré ou un tiret cadratin collé par la correction automatique d'un éditeur invalide la rangée.
- Les espaces qui alignent les colonnes comptent-ils ?
- Non. | a | b | et |a|b| produisent le même HTML sur GitHub, et l'interrupteur « Aligner les colonnes » n'existe que pour rendre le source lisible dans un éditeur. L'alignement a néanmoins un coût réel dans un dépôt : la largeur de chaque colonne étant celle de sa plus longue valeur, modifier une cellule peut changer l'alignement de toute la colonne, et un changement d'un mot devient un diff qui touche chaque rangée. Si le tableau vit dans un fichier versionné et change souvent, le générer sans alignement donne des diffs plus propres. S'il est écrit une fois et lu par des humains dans le fichier brut, garde l'alignement.
- Peut-on mettre du gras, des liens ou du code dans une cellule ?
- Oui — la mise en forme en ligne fonctionne normalement dans les cellules : **gras**, [lien](https://example.com) et `code` s'affichent tous. Les constructions de bloc, non : ni titres, ni listes, ni blocs de code délimités, ni tableaux imbriqués, car tous exigent des sauts de ligne que la rangée ne peut pas contenir. Le piège est un fragment de code contenant une barre, comme `ps | grep node`. Les accents graves ne protègent pas une barre de l'analyseur de tableau — la cellule est découpée d'abord, le code interprété ensuite — il faut donc quand même écrire `ps \| grep node`. C'est l'un des rares cas où l'échappement doit être fait à la main, le convertisseur n'échappant que les barres présentes dans les données sources.
- Pourquoi mon CSV français est-il ressorti en une seule colonne ?
- Parce que la ligne d'en-tête n'a rien donné à compter au détecteur, et c'est l'en-tête qui décide quels délimiteurs sont seulement en lice. L'outil compte les virgules, points-virgules, tabulations et barres hors guillemets sur les premiers enregistrements, et un compte qui se répète l'emporte sur celui qui est simplement le plus grand ligne 1 — mais un délimiteur absent du premier enregistrement n'est pas candidat du tout. Un tableur français ou allemand exporte en points-virgules, puisque la virgule est le séparateur décimal, et un en-tête Nom;Prénom est détecté correctement. Un en-tête réduit à un seul mot sans séparateur, non : il n'y a rien à compter, la devinette retombe sur la virgule, et tout le fichier arrive en une colonne. Règle l'option Délimiteur sur Point-virgule à la main. Le même remède vaut pour un export tabulé collé depuis un terminal, où les tabulations ont pu devenir des espaces en route et où il ne reste vraiment plus rien à trouver.
Articles qui pourraient t'intéresser
Tous les guides →Outils similaires
Ceci décrit le comportement d'un format de fichier et d'un moteur de rendu, vérifié sur la spécification citée et sur le code de l'outil tel qu'il existe aujourd'hui. Les moteurs de rendu divergent : GitHub, GitLab, un générateur de site statique et l'aperçu de ton éditeur sont quatre implémentations différentes, et ce qui passe dans l'une peut échouer dans l'autre. Rien ici n'est une garantie sur ta chaîne de publication — teste le résultat là où il sera réellement publié, et considère tout outil, celui-ci compris, comme quelque chose à vérifier et non à croire.
Sources
- GitHub — GitHub Flavored Markdown Spec, version 0.29-gfm (2019-04-06), section 4.10 Tables (extension): the delimiter row consists of cells whose only content are hyphens with optional leading or trailing colons; the header row must match the delimiter row in the number of cells or the table is not recognised; a table with no body rows generates no tbody
- GitHub Docs — Organizing information with tables: the pipe must be escaped as \| inside a cell, cells can carry inline formatting and links, and the vertical bars of a row need not line up
- RFC Editor — RFC 4180, Common Format and MIME Type for Comma-Separated Values (CSV) Files, October 2005 — section 2 rules 5 to 7: a field containing the delimiter, a line break or a double quote must be enclosed in double quotes, and an embedded double quote is written twice
- CommonMark — CommonMark Spec version 0.31.2 (2024-01-28): the core specification defines leaf and container blocks and contains no table construct — pipe tables are an extension, which is why a table that renders on GitHub may not render in a strict CommonMark processor
Tu as repéré une erreur dans cet article ?