Ir al contenido
OneKitly

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

Daniel OkonkwoDesarrollador front-end y redactor de Tecnología en OneKitly

Rendimiento web · Formatos de archivo

Verificado con 7 fuentes

Ver perfil
En resumen

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.

Lo que exige la guía de estilo propia de cada ecosistema, y qué lo impone de verdad: una convención, un linter o el propio analizador.
EcosistemaVariables y funcionesTipos y clasesConstantesQué lo impone
Python (PEP 8)snake_caseCapWordsUPPER_SNAKE_CASESolo convención; los linters avisan, el intérprete acepta cualquier cosa
Rustsnake_caseUpperCamelCaseSCREAMING_SNAKE_CASEEl compilador: non_snake_case y non_camel_case_types avisan por defecto
GomixedCapsMixedCapsMixedCaps, nunca guiones bajosEl compilador: la mayúscula inicial es lo que exporta el identificador, así que la caja es semántica, no estilo
Java (Google Java Style)lowerCamelCaseUpperCamelCaseUPPER_SNAKE_CASEConvención, más la regla explícita de que las siglas se escriben como palabras: XmlHttpRequest, no XMLHTTPRequest
JavaScript y TypeScriptcamelCasePascalCaseUPPER_SNAKE_CASENo hay guía oficial; la gramática solo descarta el guion, porque es el operador menos
CSS y HTMLkebab-case para propiedades, clases y propiedades personalizadasCSS no tiene tipos definidos por el usuario; los nombres de elementos HTML van en minúsculaspropiedades personalizadas en kebab-case, con prefijo de dos guionesLa 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
PostgreSQLsnake_case para tablas y columnassnake_case para tipos y dominiosUPPER_SNAKE_CASE solo por convenciónEl 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
Conversor a kebab-caseConvierte cualquier texto o identificador camelCase en kebab-case en minúsculas (con guiones).Probar la herramienta

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
ExplicacióncamelCase vs snake_case: guía de las convenciones de nombres en el códigocamelCase, snake_case, PascalCase y kebab-case explicados: cómo se ve cada uno, dónde es la convención y cómo elegir uno de forma coherente.GuíaCodificación de URL explicada: el porcentaje y dónde muerdeLa codificación por porcentaje se decide componente a componente, y de ahí viene toda la confusión. Una barra es legal en una ruta y debe escaparse en un valor de consulta; un espacio es %20 en una ruta y puede ser + en un cuerpo de formulario. Aquí están los conjuntos exactos de la RFC 3986, las tres funciones de JavaScript que discrepan y las trampas.ExplicaciónEl cifrado César explicado: cómo funcionan el desplazamiento y ROT13El cifrado César desplaza cada letra una cantidad fija. Descubre cómo funciona el desplazamiento, por qué ROT13 es un caso especial, cómo codificar y decodificar a mano, y por qué el cifrado no ofrece hoy seguridad real.TutorialCómo convertir JSON a CSV: aplanar arreglos de objetos en filas y columnasUna guía práctica para convertir un arreglo JSON de objetos en un archivo CSV limpio, incluido el aplanado de campos anidados y los casos límite.ExplicaciónQuitar los acentos rompe la búsqueda — hasta que lo haces en los dos ladosPlegar los diacríticos es un paso de normalización, y la normalización solo funciona cuando la misma función corre sobre el índice y sobre la consulta. NFC frente a NFD con los puntos de código a la vista, y las letras — ø, ł, ß, œ, ı — que salen del plegado intactas.ExplicaciónLos hashtags son un índice de búsqueda, no un megáfonoUn hashtag hace que una publicación sea localizable en una consulta, que es un trabajo distinto de hacer que se difunda. Modela la visibilidad que una etiqueta compra de verdad y la respuesta cae sola: el valor de una etiqueta es su número de espectadores por publicación publicada, no su volumen — así que una etiqueta muy popular no devuelve casi nada y una específica devuelve veinticinco veces más.

Herramientas relacionadas

Fuentes

¿Has detectado un error en este artículo?