camelCase, snake_case, kebab-case: cuál usar, y por qué rara vez eliges tú
Publicado el 3/7/2026 · 13 min de lectura · Herramientas de texto e idioma
Daniel Okonkwo — Desarrollador front-end y redactor de Tecnología en OneKitly
Rendimiento web · Formatos de archivo
Verificado con 7 fuentes
Cada convención viene impuesta por lo que permite la sintaxis que la rodea, no elegida por gusto. En casi todo lenguaje infijo el guion es el operador de resta, así que user-name se analiza como user menos name y no puede ser un identificador: JavaScript lanza un SyntaxError con var user-name. Ese solo hecho divide el mundo. Los nombres de propiedades CSS, los atributos HTML y las rutas de URL viven en gramáticas donde los identificadores nunca son expresiones, así que ahí el guion no es ambiguo y kebab-case es el estilo nativo. Los lenguajes de la familia Lisp admiten identificadores kebab por la misma razón: son prefijos, no infijos. Todo lo demás se asienta en snake_case o camelCase, y la elección es por ecosistema: PEP 8 impone snake_case para funciones y variables de Python; Rust hace lo mismo y su compilador avisa por defecto; Go impone MixedCaps y hace semántica la mayúscula inicial, porque controla la exportación; Java y JavaScript usan lowerCamelCase con tipos en PascalCase. La trampa es el viaje de ida y vuelta. Convertir camelCase a snake_case y volver no es sin pérdidas cuando hay siglas: parseHTMLDocument pasa a parse_htmldocument y luego a parseHtmldocument con un conversor ingenuo, y la frontera de palabra se ha perdido para siempre. Arréglalo en el origen tratando las siglas como palabras normales, como exige la guía de estilo Java de Google. Y un slug de URL es una cuarta cosa: en minúsculas, sin acentos y con longitud limitada.
Las convenciones no son cuestión de gusto. El guion es el operador menos, así que kebab-case no puede ser un identificador en la mayoría de lenguajes, y por eso justamente lo usan CSS y las URL. Además, el viaje de ida y vuelta con siglas que corrompe nombres en silencio, y la regla que lo arregla.
El guion es el operador menos, y esa es toda la explicación
Escribe var user-name = 1 en Node y obtienes SyntaxError: Unexpected token '-'. El analizador no está siendo quisquilloso. En un lenguaje infijo, a-b en posición de expresión significa restar b de a, y el tokenizador no tiene forma de saber que querías un identificador en vez de dos operandos. Python, Java, C, C#, Go, Rust, PHP, Ruby y SQL comparten esta restricción. El guion ya está pedido.
Eso deja exactamente dos maneras de unir palabras dentro de un identificador: el guion bajo, que ningún lenguaje usa como operador, o la mayúscula, que no es un carácter separador en el sentido del tokenizador. snake_case y camelCase no son dos escuelas estéticas. Son las dos únicas soluciones a una restricción impuesta por la aritmética.
La excepción confirma la regla. Lisp, Scheme, Clojure y Common Lisp permiten identificadores en kebab-case - make-hash-table, my-function-name - porque son lenguajes prefijos: la resta se escribe (- a b), así que un guion entre letras nunca puede ser un operador. Cambia la gramática y la convención de nombres cambia con ella, que es justo lo que se quiere demostrar.
Donde kebab-case es nativo: CSS, atributos HTML, URL
En CSS, background-color es un nombre de propiedad en una posición donde no se admite ninguna expresión, así que el guion no tiene con qué confundirse. La prueba de que CSS es consciente de la tensión es calc(): la especificación exige espacios alrededor de los signos más y menos dentro de él, precisamente porque ese es el único lugar de CSS donde un guion podría ser una resta o parte de un identificador. Los atributos HTML siguen la misma lógica: data-user-id es un nombre en posición de atributo, nunca una expresión.
El punto de cruce es donde se pone interesante. El DOM tiene que exponer data-user-id a JavaScript, donde el guion es ilegal, así que lo renombra: element.dataset.userId. El modelo de objetos CSS hace lo mismo con las propiedades, convirtiendo background-color en style.backgroundColor. Esas dos conversiones automáticas son la demostración más clara posible de que la convención es una función de la gramática anfitriona y de nada más: el mismo nombre, escrito de dos formas, porque dos gramáticas exigen dos grafías.
Las URL admiten tanto el guion como el guion bajo - ambos son caracteres no reservados -, así que aquí la razón es distinta y mucho más blanda. Las recomendaciones de URL de Google prefieren el guion porque se lee como separador de palabras tanto para rastreadores como para personas, y porque un guion bajo puede desaparecer bajo el subrayado de un enlace. Es un argumento de legibilidad, no gramatical, pero ha cuajado en una convención tan fuerte que hoy una URL con guiones bajos parece un error.
El viaje de ida y vuelta que pierde información
Convertir camelCase a snake_case y volver parece una biyección. No lo es, y son las siglas las que lo rompen. Un conversor ingenuo inserta un guion bajo antes de cada mayúscula que sigue a una minúscula y luego lo pasa todo a minúsculas. Mete parseHTMLDocument y obtienes parse_htmldocument, porque no hay ninguna minúscula antes de la H, la T, la M ni la L. Vuelve a convertir y obtienes parseHtmldocument. La frontera de palabra entre HTML y Document ha desaparecido, y ninguna astucia posterior puede recuperarla.
getIDFromURL es peor, porque el daño no se limita a la caja. La regla ingenua produce get_idfrom_url - se dispara entre la t y la I, y de nuevo entre la m y la U, pero no dentro de IDFrom - y al volver da getIdfromUrl. Un nombre que eran tres palabras claras se ha convertido en dos palabras destrozadas, y si esa cadena es una columna de base de datos, una clave JSON o un campo de API, la corrupción queda ya persistida.
La regla que lo arregla, y el caso que sigue fallando
El arreglo cabe en una segunda regla de frontera. Junto a la partición habitual minúscula-luego-mayúscula, añade una partición entre una racha de mayúsculas y una mayúscula seguida de minúscula. En expresiones regulares son dos pasadas: insertar un guion bajo entre ([a-z0-9]) y ([A-Z]), luego entre ([A-Z]+) y ([A-Z][a-z]), y después pasar a minúsculas. Con esas dos reglas, parseHTMLDocument pasa a parse_html_document y vuelve como parseHtmlDocument; getIDFromURL pasa a get_id_from_url y vuelve como getIdFromUrl; exportToPDFFile pasa a export_to_pdf_file. Las formas kebab son parse-html-document, get-id-from-url y export-to-pdf-file. Las fronteras de palabra sobreviven.
Fíjate en que el viaje de ida y vuelta sigue sin ser la identidad: parseHTMLDocument vuelve como parseHtmlDocument, con la sigla en caja de título. Ese es el resultado correcto, no un fallo residual, y señala el arreglo de verdad. La sección 5.3 de la guía de estilo Java de Google exige justo esto en el momento de escribir: escribe las siglas como palabras normales, o sea XmlHttpRequest en vez de XMLHTTPRequest, y el nombre se convierte en un punto fijo de la conversión. Un nombre que sobrevive a su propio viaje de ida y vuelta es un nombre que puedes pasar sin riesgo por un generador de código, un ORM, un serializador y de vuelta.
Queda un caso que la regla de dos expresiones sigue fallando, y conviene conocerlo porque parece un fallo de la regla. Las siglas de caja mixta la derrotan: supportsIPv6 pasa a supports_i_pv6, y la forma kebab es supports-i-pv6. La segunda expresión ve la P mayúscula seguida de la v minúscula y parte ahí, que es exactamente lo que debe hacer en cualquier otro sitio. Ninguna regla de frontera que solo mire la caja de las letras puede saber que IPv6 es un único token. Este es el argumento más fuerte a favor de la regla de Google: escribe supportsIpv6 desde el principio y el conversor no tendrá que adivinar nunca.
Un slug de URL es una cuarta cosa, no kebab-case con pasos extra
Un slug parece kebab-case pero tiene tres obligaciones extra que un identificador nunca tiene. Debe sobrevivir al paso a minúsculas, porque los servidores comparan las rutas de URL distinguiendo mayúsculas mientras las personas las escriben a la ligera. Debe sobrevivir al retirado de acentos, porque una ruta con caracteres acentuados se codifica en porcentajes y se vuelve ilegible. Y debe caber en un presupuesto de longitud, porque los slugs acaban en correos, material impreso y barras de direcciones donde una ruta de 200 caracteres es inservible.
El paso de retirar acentos es donde las implementaciones ingenuas pierden datos en silencio. La receta habitual es normalizar a forma descompuesta, borrar las marcas combinantes y luego conservar solo letras, dígitos y guiones. Aplicada a un título francés funciona: Crème Brûlée & Co. — 2026 Edition queda en creme-brulee-co-2026-edition, 28 caracteres. Aplicada al alemán destruye el texto. El título Größe & Maße: der Überblick sale como gro-e-ma-e-der-uberblick, porque la ese alemana no tiene descomposición canónica: no se pliega a nada, simplemente se borra junto a cualquier otro carácter que no sea letra latina.
El arreglo es transliterar antes de normalizar, con un mapa por idioma: la ese alemana a ss, las vocales con diéresis a oe, ae y ue en alemán, la o barrada y la a con anillo escandinavas a sus equivalentes de dos letras. Con ese paso por delante, el mismo título alemán da groesse-masse-der-ueberblick, 28 caracteres y de verdad legible. Haz inmutable el slug resultante una vez publicado, límítalo a unos 60 u 80 caracteres cortando en frontera de palabra, y no lo regeneres nunca desde un título editado sin emitir una redirección desde el antiguo.
Elegir, en la práctica
Sigue al anfitrión, no a tu preferencia. Dentro de un archivo Python, snake_case, aunque el JSON que analizas sea camelCase. Dentro de un archivo CSS, kebab-case, aunque los tokens de diseño se escribieran en camelCase. Dentro de un esquema PostgreSQL, snake_case, porque el analizador pasará tu camelCase a minúsculas de todas formas y te pasarás el resto del proyecto escribiendo comillas dobles.
Convierte solo en las fronteras, y en un único sitio. Si tu API habla camelCase y tu base de datos snake_case, pon una sola capa de correspondencia entre ambas en vez de convertir a la carta en cada llamada, y haz que esa capa sea el único código que conozca la regla de dos expresiones. Escribe las siglas como palabras en todas partes, para que la conversión sea un punto fijo y nadie tenga que volver a pensarlo. Y cuando generes un slug, trátalo como un identificador publicado desde el momento en que sale: es la única de estas cuatro formas que un desconocido pegará en un mensaje.
| Ecosistema | Variables y funciones | Tipos y clases | Constantes | Qué lo impone |
|---|---|---|---|---|
| Python (PEP 8) | snake_case | CapWords | UPPER_SNAKE_CASE | Solo convención; los linters avisan, el intérprete acepta cualquier cosa |
| Rust | snake_case | UpperCamelCase | SCREAMING_SNAKE_CASE | El compilador: non_snake_case y non_camel_case_types avisan por defecto |
| Go | mixedCaps | MixedCaps | MixedCaps, nunca guiones bajos | El compilador: la mayúscula inicial es lo que exporta el identificador, así que la caja es semántica, no estilo |
| Java (Google Java Style) | lowerCamelCase | UpperCamelCase | UPPER_SNAKE_CASE | Convención, más la regla explícita de que las siglas se escriben como palabras: XmlHttpRequest, no XMLHTTPRequest |
| JavaScript y TypeScript | camelCase | PascalCase | UPPER_SNAKE_CASE | No hay guía oficial; la gramática solo descarta el guion, porque es el operador menos |
| CSS y HTML | kebab-case para propiedades, clases y propiedades personalizadas | CSS no tiene tipos definidos por el usuario; los nombres de elementos HTML van en minúsculas | propiedades personalizadas en kebab-case, con prefijo de dos guiones | La gramática: un nombre de propiedad nunca es una expresión, así que un guion dentro no puede ser un menos, y por eso calc() exige espacios alrededor de sus signos menos |
| PostgreSQL | snake_case para tablas y columnas | snake_case para tipos y dominios | UPPER_SNAKE_CASE solo por convención | El analizador: los identificadores sin comillas se pliegan a minúsculas, así que un nombre de tabla en camelCase pasa a minúsculas en silencio salvo que lo entrecomilles para siempre |
Preguntas frecuentes
- ¿Por qué puedo escribir background-color en CSS pero no backgroundColor en una hoja de estilos?
- Porque los nombres de propiedades CSS son un vocabulario fijo definido por la especificación, y esta los escribe en kebab-case. No es que camelCase sea ilegal en la gramática: es que backgroundColor no es el nombre de ninguna propiedad, así que la declaración se descarta como desconocida. Las grafías en camelCase existen solo en el modelo de objetos CSS, la vista de JavaScript de un estilo, donde el guion sería un signo menos. Dos grafías, dos gramáticas, una propiedad.
- ¿Se lee mejor camelCase o snake_case?
- Los trabajos publicados de seguimiento ocular sobre esto son escasos, antiguos y discutidos, y no sostienen ninguna afirmación fuerte en ningún sentido: algunos estudios encuentran snake_case marginalmente más rápido de leer, otros encuentran que los lectores entrenados van más rápido en el estilo que practican a diario. Lo que no se discute es el coste de la incoherencia dentro de una misma base de código. Toma lo que exija el ecosistema, hazlo cumplir con un formateador y gasta el presupuesto de discusión en algo que cambie comportamientos.
- ¿Cómo convierto con seguridad el JSON camelCase de una API a una base de datos en snake_case?
- Usa la regla de frontera de dos pasadas, aplícala en un único módulo y fija la correspondencia de cualquier nombre que contenga una sigla. La versión pragmática es mantener una tabla de excepciones explícita: un diccionario corto con los quince o veinte nombres de campo de tu esquema cuya conversión no quieres que decida una expresión regular. Esa tabla cuesta una hora de escritura y elimina toda la clase de fallo, mientras que una correspondencia puramente algorítmica acabará encontrándose un nombre como supportsIPv6 y produciendo algo que ningún revisor nota hasta que una consulta no devuelve nada.
- ¿Puedo usar camelCase para nombres de tablas y columnas en PostgreSQL?
- Puedes, pero solo entrecomillando el identificador cada vez que aparece, para siempre, en cada consulta, migración, vista y script. PostgreSQL pliega a minúsculas los identificadores sin comillas, así que una tabla creada como userAccounts pasa a ser useraccounts, y una consulta posterior por userAccounts la encuentra solo porque también se pliega a useraccounts, hasta el día en que alguien entrecomilla una de las dos y dejan de coincidir. Aquí snake_case no es una preferencia de estilo: es la forma que atraviesa el analizador intacta.
- ¿Debe un slug de URL ser solo el título en kebab-case?
- Casi, pero con tres añadidos que el kebab-case por sí solo no da. Pásalo todo a minúsculas, porque una ruta que solo difiere en la caja es otro recurso para un servidor pero la misma cosa para una persona. Translitera antes de quitar los acentos, o caracteres como la ese alemana se desvanecen por completo en vez de convertirse en ss. Y limita la longitud en una frontera de palabra, en torno a 60 u 80 caracteres, ya que el slug se pegará en sitios sin espacio. Una regla más, ajena a la caja: una vez publicado, no lo cambies nunca sin una redirección permanente desde la ruta antigua.
Artículos que podrían interesarte
Todas las guías →Herramientas relacionadas
Fuentes
- Python Software Foundation — PEP 8: Style Guide for Python Code - Naming Conventions
- Google — Google Java Style Guide, section 5.3: Camel case: defined
- The Go Authors — Effective Go: Names (MixedCaps, and the initial capital as the export rule)
- The Rust Project — Rust API Guidelines: Naming
- PostgreSQL Global Development Group — PostgreSQL Documentation: Lexical Structure - Identifiers and Key Words
- W3C — CSS Values and Units Module Level 4 (whitespace required around plus and minus in calc())
- Google Search Central — URL structure best practices for Google
¿Has detectado un error en este artículo?