Aller au contenu
OneKitly

camelCase, snake_case, kebab-case : laquelle, et pourquoi tu choisis rarement

Publié le 03/07/2026 · 13 min de lecture · Outils texte & langage

Daniel Okonkwo

Daniel OkonkwoDéveloppeur front-end et rédacteur Tech chez OneKitly

Performance web · Formats de fichiers

Vérifié à partir de 7 sources

Voir le profil
En bref

Chaque convention est imposée par ce que la syntaxe environnante autorise, pas choisie par goût. Dans presque tout langage infixe, le trait d'union est l'opérateur de soustraction : user-name s'analyse comme user moins name et ne peut pas être un identifiant - JavaScript lève une SyntaxError sur var user-name. Ce seul fait sépare le monde en deux. Les noms de propriétés CSS, les attributs HTML et les chemins d'URL vivent dans des grammaires où les identifiants ne sont jamais des expressions : le trait d'union y est sans ambiguïté et kebab-case est le style natif. Les langages de la famille Lisp acceptent les identifiants kebab pour la même raison : ils sont préfixes, pas infixes. Tout le reste se range sur snake_case ou camelCase, et le choix se fait par écosystème : PEP 8 impose snake_case pour les fonctions et variables Python ; Rust fait de même et son compilateur avertit par défaut ; Go impose MixedCaps et rend la majuscule initiale sémantique, puisqu'elle commande l'exportation ; Java et JavaScript utilisent lowerCamelCase avec des types en PascalCase. Le piège, c'est l'aller-retour. Convertir camelCase en snake_case puis revenir n'est pas sans perte dès qu'un acronyme apparaît : parseHTMLDocument devient parse_htmldocument puis parseHtmldocument avec un convertisseur naïf, et la frontière de mot a disparu pour de bon. Corrige à la source en traitant les acronymes comme des mots ordinaires, comme l'exige le guide de style Java de Google. Et un slug d'URL est une quatrième chose : minuscules, accents retirés, longueur plafonnée.

Les conventions ne relèvent pas du goût. Le trait d'union est l'opérateur moins : kebab-case ne peut donc pas être un identifiant dans la plupart des langages - et c'est précisément pourquoi CSS et les URL l'utilisent. Avec en prime l'aller-retour sur les acronymes qui corrompt les noms en silence, et la règle qui le corrige.

Le trait d'union est l'opérateur moins, et c'est toute l'explication

Tape var user-name = 1 dans Node et tu obtiens SyntaxError: Unexpected token '-'. L'analyseur ne fait pas de zèle. Dans un langage infixe, a-b en position d'expression signifie soustraire b de a, et le tokeniseur n'a aucun moyen de savoir que tu voulais un identifiant et non deux opérandes. Python, Java, C, C#, Go, Rust, PHP, Ruby et SQL partagent tous cette contrainte. Le trait d'union est déjà pris.

Il ne reste alors que deux façons de relier des mots à l'intérieur d'un identifiant : le tiret bas, qu'aucun langage n'utilise comme opérateur, ou la majuscule, qui n'est pas un caractère séparateur au sens du tokeniseur. snake_case et camelCase ne sont pas deux écoles esthétiques. Ce sont les deux seules solutions à une contrainte imposée par l'arithmétique.

L'exception confirme la règle. Lisp, Scheme, Clojure et Common Lisp autorisent tous les identifiants en kebab-case - make-hash-table, my-function-name - parce que ce sont des langages préfixes : la soustraction s'écrit (- a b), un trait d'union entre lettres ne peut donc jamais être un opérateur. Change la grammaire et la convention de nommage change avec elle, ce qui est précisément la démonstration.

Là où kebab-case est natif : CSS, attributs HTML, URL

En CSS, background-color est un nom de propriété dans une position où aucune expression n'est admise : le trait d'union n'a rien avec quoi être confondu. La preuve que CSS a conscience de la tension, c'est calc() : la spécification exige des espaces autour des plus et des moins à l'intérieur, précisément parce que c'est le seul endroit de CSS où un trait d'union pourrait être soit une soustraction, soit une partie d'identifiant. Les attributs HTML suivent la même logique : data-user-id est un nom en position d'attribut, jamais une expression.

C'est au point de passage que cela devient intéressant. Le DOM doit exposer data-user-id à JavaScript, où le trait d'union est illégal : il le renomme donc en element.dataset.userId. Le modèle objet CSS fait de même avec les propriétés, transformant background-color en style.backgroundColor. Ces deux conversions automatiques sont la démonstration la plus claire possible que la convention est une fonction de la grammaire hôte et de rien d'autre : un même nom, deux orthographes, parce que deux grammaires en exigent deux.

Les URL admettent aussi bien le trait d'union que le tiret bas - tous deux sont des caractères non réservés - la raison est donc ici différente et bien plus douce. Les recommandations d'URL de Google privilégient le trait d'union parce qu'il se lit comme un séparateur de mots pour les robots comme pour les humains, et parce qu'un tiret bas peut disparaître sous le soulignement d'un lien. C'est un argument de lisibilité, pas de grammaire, mais il s'est durci en une convention assez forte pour qu'une URL à tirets bas ressemble aujourd'hui à une erreur.

L'aller-retour qui perd de l'information

Convertir camelCase en snake_case puis revenir ressemble à une bijection. Ce n'en est pas une, et ce sont les acronymes qui cassent. Un convertisseur naïf insère un tiret bas avant chaque majuscule qui suit une minuscule, puis met tout en bas de casse. Passe parseHTMLDocument dedans et tu obtiens parse_htmldocument, car il n'y a aucune minuscule avant le H, le T, le M ni le L. Reconvertis et tu obtiens parseHtmldocument. La frontière de mot entre HTML et Document a disparu, et aucune astuce en aval ne peut la récupérer.

getIDFromURL est pire, car les dégâts ne se limitent pas à la casse. La règle naïve produit get_idfrom_url - elle se déclenche entre le t et le I, puis entre le m et le U, mais pas à l'intérieur de IDFrom - et le retour donne getIdfromUrl. Un nom qui était trois mots clairs est devenu deux mots estropiés, et si cette chaîne est une colonne de base de données, une clé JSON ou un champ d'API, la corruption est désormais persistée.

La règle qui corrige, et le cas qu'elle rate encore

La correction tient dans une seconde règle de frontière. À la coupure habituelle minuscule-puis-majuscule, ajoute une coupure entre une série de majuscules et une majuscule suivie d'une minuscule. En expressions régulières, cela fait deux passes : insérer un tiret bas entre ([a-z0-9]) et ([A-Z]), puis entre ([A-Z]+) et ([A-Z][a-z]), puis tout mettre en minuscules. Avec ces deux règles, parseHTMLDocument devient parse_html_document et revient en parseHtmlDocument ; getIDFromURL devient get_id_from_url et revient en getIdFromUrl ; exportToPDFFile devient export_to_pdf_file. Les formes kebab sont parse-html-document, get-id-from-url et export-to-pdf-file. Les frontières de mots survivent.

Note que l'aller-retour n'est toujours pas l'identité : parseHTMLDocument revient en parseHtmlDocument, l'acronyme passé en capitale initiale. C'est le bon résultat, pas un bug résiduel, et il désigne la vraie solution. La section 5.3 du guide de style Java de Google impose exactement cela à l'écriture : écris les acronymes comme des mots ordinaires, donc XmlHttpRequest plutôt que XMLHTTPRequest, et le nom devient un point fixe de la conversion. Un nom qui survit à son propre aller-retour est un nom que tu peux faire passer sans risque par un générateur de code, un ORM, un sérialiseur et retour.

Il reste un cas que la règle à deux expressions rate, et il vaut d'être connu car il ressemble à un échec de la règle. Les acronymes à casse mixte la mettent en défaut : supportsIPv6 devient supports_i_pv6, et la forme kebab est supports-i-pv6. La seconde expression voit le P majuscule suivi du v minuscule et coupe là, ce qu'elle est précisément censée faire partout ailleurs. Aucune règle de frontière qui ne regarde que la casse ne peut savoir qu'IPv6 est un seul jeton. C'est l'argument le plus fort en faveur de la règle Google : écris supportsIpv6 dès le départ, et le convertisseur n'a plus jamais à deviner.

Un slug d'URL est une quatrième chose, pas du kebab-case avec des étapes en plus

Un slug ressemble à du kebab-case mais porte trois obligations supplémentaires qu'un identifiant n'a jamais. Il doit survivre au passage en minuscules, car les chemins d'URL sont comparés en tenant compte de la casse par les serveurs mais tapés à la légère par les humains. Il doit survivre au retrait des accents, car un chemin à caractères accentués est encodé en pourcentages et devient illisible. Et il doit tenir dans un budget de longueur, car les slugs finissent dans des courriels, des supports imprimés et des barres d'adresse où un chemin de 200 caractères est inutilisable.

L'étape de retrait des accents est celle où les implémentations naïves perdent des données en silence. La recette habituelle consiste à normaliser en forme décomposée, à supprimer les signes combinants, puis à ne garder que lettres, chiffres et traits d'union. Appliquée à un titre français, elle marche : Crème Brûlée & Co. — 2026 Edition devient creme-brulee-co-2026-edition, soit 28 caractères. Appliquée à l'allemand, elle détruit le texte. Le titre Größe & Maße: der Überblick ressort en gro-e-ma-e-der-uberblick - car le s dur n'a aucune décomposition canonique : il n'est donc replié sur rien, il est purement et simplement supprimé, comme tout caractère qui n'est pas une lettre latine.

La solution consiste à translittérer avant de normaliser, à l'aide d'une table par langue : le s dur en ss, les voyelles à tréma en oe, ae et ue en allemand, le o barré et le a rond scandinaves vers leurs équivalents à deux lettres. Avec cette étape en tête de chaîne, le même titre allemand donne groesse-masse-der-ueberblick, soit 28 caractères, et réellement lisible. Rendez le slug obtenu immuable une fois publié, plafonne-le vers 60 à 80 caractères en coupant sur une frontière de mot, et ne le régénère jamais depuis un titre modifié sans poser une redirection depuis l'ancien.

Choisir, en pratique

Suis l'hôte, pas ta préférence. Dans un fichier Python, snake_case, même si le JSON que tu analyses est en camelCase. Dans un fichier CSS, kebab-case, même si les jetons de design ont été écrits en camelCase. Dans un schéma PostgreSQL, snake_case, car l'analyseur mettra de toute façon ton camelCase en minuscules et tu passeras le reste du projet à écrire des guillemets doubles.

Ne convertis qu'aux frontières, et à un seul endroit. Si ton API parle camelCase et ta base snake_case, place une couche de correspondance unique entre les deux plutôt que de convertir au coup par coup à chaque appel, et fais de cette couche le seul code qui connaisse la règle à deux expressions. Écris les acronymes comme des mots partout, pour que la conversion soit un point fixe et que personne n'ait plus à y penser. Et lorsque tu génères un slug, traite-le comme un identifiant publié dès sa mise en ligne : c'est la seule de ces quatre formes qu'un inconnu collera dans un message.

Ce qu'impose le guide de style propre à chaque écosystème, et ce qui l'impose réellement : une convention, un linter, ou l'analyseur lui-même.
ÉcosystèmeVariables et fonctionsTypes et classesConstantesCe qui l'impose
Python (PEP 8)snake_caseCapWordsUPPER_SNAKE_CASEConvention seule ; les linters avertissent, l'interpréteur accepte tout
Rustsnake_caseUpperCamelCaseSCREAMING_SNAKE_CASELe compilateur : non_snake_case et non_camel_case_types avertissent par défaut
GomixedCapsMixedCapsMixedCaps, jamais de tirets basLe compilateur : la majuscule initiale rend l'identifiant exporté, la casse est donc sémantique, pas stylistique
Java (Google Java Style)lowerCamelCaseUpperCamelCaseUPPER_SNAKE_CASEConvention, plus la règle explicite selon laquelle les acronymes s'écrivent comme des mots : XmlHttpRequest, pas XMLHTTPRequest
JavaScript et TypeScriptcamelCasePascalCaseUPPER_SNAKE_CASEAucun guide officiel ; la grammaire n'exclut que le trait d'union, parce qu'il est l'opérateur moins
CSS et HTMLkebab-case pour les propriétés, les classes et les propriétés personnaliséesCSS n'a pas de types définis par l'utilisateur ; les noms d'éléments HTML sont en minusculespropriétés personnalisées en kebab-case, préfixées de deux traits d'unionLa grammaire : un nom de propriété n'est jamais une expression, un trait d'union en son sein ne peut donc pas être un moins - d'où l'exigence d'espaces autour des moins dans calc()
PostgreSQLsnake_case pour les tables et les colonnessnake_case pour les types et les domainesUPPER_SNAKE_CASE par simple conventionL'analyseur : les identifiants non guillemetés sont ramenés en minuscules, un nom de table en camelCase devient donc silencieusement minuscule à moins de le guillemeter à jamais
Convertisseur kebab-caseConvertis n'importe quel texte ou identifiant camelCase en kebab-case minuscule (avec tirets).Essayer l'outil

Questions fréquentes

Pourquoi puis-je écrire background-color en CSS mais pas backgroundColor dans une feuille de style ?
Parce que les noms de propriétés CSS forment un vocabulaire fixe défini par la spécification, et que celle-ci les orthographie en kebab-case. Ce n'est pas que le camelCase soit illégal dans la grammaire : c'est que backgroundColor n'est le nom d'aucune propriété, la déclaration est donc ignorée comme inconnue. Les graphies en camelCase n'existent que dans le modèle objet CSS, la vue JavaScript d'un style, où le trait d'union serait un signe moins. Deux graphies, deux grammaires, une seule propriété.
camelCase ou snake_case, lequel se lit le plus facilement ?
Les travaux publiés en oculométrie sur ce sujet sont peu nombreux, anciens et contestés ; ils n'étayent aucune affirmation forte dans un sens ou dans l'autre - certaines études trouvent snake_case marginalement plus rapide à lire, d'autres trouvent que les lecteurs entraînés sont plus rapides dans le style qu'ils pratiquent au quotidien. Ce qui n'est pas contesté, c'est le coût de l'incohérence à l'intérieur d'une même base de code. Prends ce que l'écosystème impose, fais-le appliquer par un formateur, et dépense ton capital de discussion sur un sujet qui change les comportements.
Comment convertir sans risque le JSON camelCase d'une API vers une base en snake_case ?
Utilise la règle de frontière en deux passes, applique-la dans un seul module, et fige la correspondance pour tout nom contenant un acronyme. La version pragmatique consiste à tenir une table de dérogations explicite : un court dictionnaire des quinze ou vingt noms de champs de ton schéma dont tu ne veux pas qu'une expression régulière décide de la conversion. Cette table coûte une heure à écrire et supprime toute la classe de bugs, alors qu'une correspondance purement algorithmique finira par croiser un nom comme supportsIPv6 et produire quelque chose qu'aucun relecteur ne remarque avant qu'une requête ne renvoie rien.
Puis-je utiliser camelCase pour les noms de tables et de colonnes PostgreSQL ?
Tu le peux, mais seulement en guillemetant l'identifiant à chaque occurrence, pour toujours, dans chaque requête, migration, vue et script. PostgreSQL ramène les identifiants non guillemetés en minuscules : une table créée sous le nom userAccounts devient useraccounts, et une requête ultérieure sur userAccounts la trouve uniquement parce qu'elle est elle aussi ramenée à useraccounts - jusqu'au jour où quelqu'un en guillemète une et où les deux cessent de correspondre. Ici, snake_case n'est pas une préférence de style : c'est la forme qui traverse l'analyseur intacte.
Un slug d'URL doit-il être simplement le titre en kebab-case ?
Presque, mais avec trois ajouts que le kebab-case seul ne fournit pas. Tout mettre en minuscules, car un chemin qui ne diffère que par la casse est une autre ressource pour un serveur mais la même chose pour une personne. Translittérer avant de retirer les accents, sans quoi des caractères comme le s dur allemand disparaissent purement et simplement au lieu de devenir ss. Et plafonner la longueur sur une frontière de mot, autour de 60 à 80 caractères, puisque le slug sera collé dans des endroits sans place. Une règle de plus, sans rapport avec la casse : une fois publié, ne le change jamais sans redirection permanente depuis l'ancien chemin.

Articles qui pourraient t'intéresser

Tous les guides
ExplicationcamelCase vs snake_case : guide des conventions de nommage dans le codecamelCase, snake_case, PascalCase et kebab-case expliqués : à quoi chacun ressemble, où c'est la convention, et comment en choisir un de façon cohérente.GuideL'encodage d'URL expliqué : le pourcentage et là où ça coinceL'encodage par pourcentage se décide composant par composant, et c'est de là que vient toute la confusion. Une barre oblique est légale dans un chemin et doit être échappée dans une valeur de requête ; une espace est %20 dans un chemin et peut être + dans un corps de formulaire. Voici les ensembles exacts de la RFC 3986, les trois fonctions JavaScript qui divergent, et les pièges.ExplicationLe chiffre de César expliqué : comment fonctionnent le décalage et ROT13Le chiffre de César décale chaque lettre d'une valeur fixe. Découvre comment fonctionne le décalage, pourquoi ROT13 est un cas particulier, comment coder et décoder à la main, et pourquoi ce chiffre n'offre aujourd'hui aucune sécurité réelle.TutorielComment convertir du JSON en CSV : aplatir des tableaux d'objets en lignes et colonnesUn guide pratique pour transformer un tableau JSON d'objets en fichier CSV propre, avec l'aplatissement des champs imbriqués et la gestion des cas limites.ExplicationRetirer les accents casse la recherche — tant que tu ne le fais pas des deux côtésReplier les diacritiques est une étape de normalisation, et une normalisation ne fonctionne que si la même fonction s'applique à l'index et à la requête. NFC contre NFD avec les points de code affichés, et les lettres — ø, ł, ß, œ, ı — qui sortent du repli intactes.ExplicationLes hashtags sont un index de recherche, pas un mégaphoneUn hashtag rend un post trouvable dans une requête, ce qui n'est pas le même travail que le faire circuler. Modélise la visibilité qu'un tag achète réellement et la réponse tombe : la valeur d'un tag vaut son nombre de spectateurs par post publié, pas son volume — un tag très populaire ne rapporte donc presque rien et un tag spécifique vingt-cinq fois plus.

Outils similaires

Sources

Tu as repéré une erreur dans cet article ?