camelCase, snake_case, kebab-case: qual usar, e porque raramente és tu a escolher
Publicado a 03/07/2026 · 13 min de leitura · Ferramentas de texto e idioma
Daniel Okonkwo — Programador front-end e redator de Tecnologia na OneKitly
Desempenho web · Formatos de ficheiro
Verificado a partir de 7 fontes
Cada convenção é imposta pelo que a sintaxe à volta permite, não escolhida por gosto. Em quase toda a linguagem infixa o hífen é o operador de subtração, pelo que user-name é analisado como user menos name e não pode ser um identificador: o JavaScript lança um SyntaxError com var user-name. Esse único facto divide o mundo. Os nomes de propriedades CSS, os atributos HTML e os caminhos de URL vivem em gramáticas onde os identificadores nunca são expressões, pelo que aí o hífen é inequívoco e kebab-case é o estilo nativo. As linguagens da família Lisp aceitam identificadores kebab pela mesma razão: são prefixas, não infixas. Todo o resto assenta em snake_case ou camelCase, e a escolha é por ecossistema: a PEP 8 impõe snake_case para funções e variáveis Python; o Rust faz o mesmo e o seu compilador avisa por omissão; o Go impõe MixedCaps e torna semântica a maiúscula inicial, porque controla a exportação; Java e JavaScript usam lowerCamelCase com tipos em PascalCase. A armadilha é a ida e volta. Converter camelCase em snake_case e regressar não é sem perdas quando há siglas: parseHTMLDocument passa a parse_htmldocument e depois a parseHtmldocument com um conversor ingénuo, e a fronteira de palavra desapareceu de vez. Corrija na origem tratando as siglas como palavras normais, como exige o guia de estilo Java da Google. E um slug de URL é uma quarta coisa: minúsculas, sem acentos e com comprimento limitado.
As convenções não são gosto. O hífen é o operador menos, pelo que kebab-case não pode ser um identificador na maioria das linguagens - e é exatamente por isso que o CSS e os URL o usam. Mais a ida e volta com siglas que corrompe nomes em silêncio, e a regra que a corrige.
O hífen é o operador menos, e é essa toda a explicação
Escreva var user-name = 1 no Node e obtém SyntaxError: Unexpected token '-'. O analisador não está a ser chato. Numa linguagem infixa, a-b em posição de expressão significa subtrair b de a, e o tokenizador não tem como saber que queria um identificador em vez de dois operandos. Python, Java, C, C#, Go, Rust, PHP, Ruby e SQL partilham todos esta restrição. O hífen já está tomado.
Isso deixa exatamente duas formas de juntar palavras dentro de um identificador: o sublinhado, que nenhuma linguagem usa como operador, ou a maiúscula, que não é um caráter separador no sentido do tokenizador. snake_case e camelCase não são duas escolas estéticas. São as duas únicas soluções para uma restrição imposta pela aritmética.
A exceção confirma a regra. Lisp, Scheme, Clojure e Common Lisp permitem todos identificadores em kebab-case - make-hash-table, my-function-name - porque são linguagens prefixas: a subtração escreve-se (- a b), pelo que um hífen entre letras nunca pode ser um operador. Mude a gramática e a convenção de nomes muda com ela, que é exatamente a demonstração.
Onde kebab-case é nativo: CSS, atributos HTML, URL
Em CSS, background-color é um nome de propriedade numa posição onde nenhuma expressão é admitida, pelo que o hífen não tem com que ser confundido. A prova de que o CSS tem consciência da tensão é calc(): a especificação exige espaços à volta dos sinais de mais e de menos lá dentro, precisamente porque é o único sítio do CSS onde um hífen podia ser uma subtração ou parte de um identificador. Os atributos HTML seguem a mesma lógica: data-user-id é um nome em posição de atributo, nunca uma expressão.
É no ponto de cruzamento que fica interessante. O DOM tem de expor data-user-id ao JavaScript, onde o hífen é ilegal, pelo que o renomeia: element.dataset.userId. O modelo de objetos CSS faz o mesmo às propriedades, transformando background-color em style.backgroundColor. Essas duas conversões automáticas são a demonstração mais clara possível de que a convenção é uma função da gramática anfitriã e de mais nada: o mesmo nome, escrito de duas formas, porque duas gramáticas exigem duas grafias.
Os URL admitem tanto o hífen como o sublinhado - ambos são carateres não reservados -, pelo que aqui a razão é diferente e muito mais branda. As recomendações de URL da Google preferem o hífen porque se lê como separador de palavras tanto para rastreadores como para pessoas, e porque um sublinhado pode desaparecer sob o sublinhado de uma ligação. É um argumento de legibilidade, não gramatical, mas endureceu numa convenção tão forte que hoje um URL com sublinhados parece um erro.
A ida e volta que perde informação
Converter camelCase em snake_case e voltar parece uma bijeção. Não é, e são as siglas que a partem. Um conversor ingénuo insere um sublinhado antes de cada maiúscula que segue uma minúscula e depois passa tudo a minúsculas. Passe parseHTMLDocument por ele e obtém parse_htmldocument, porque não há nenhuma minúscula antes do H, do T, do M nem do L. Converta de volta e obtém parseHtmldocument. A fronteira de palavra entre HTML e Document desapareceu, e nenhuma esperteza a jusante a consegue recuperar.
getIDFromURL é pior, porque o dano não se limita à caixa. A regra ingénua produz get_idfrom_url - dispara entre o t e o I, e de novo entre o m e o U, mas não dentro de IDFrom - e o regresso dá getIdfromUrl. Um nome que eram três palavras claras tornou-se duas palavras estropiadas, e se essa cadeia for uma coluna de base de dados, uma chave JSON ou um campo de API, a corrupção ficou persistida.
A regra que corrige, e o caso que continua a falhar
A correção cabe numa segunda regra de fronteira. Ao lado da divisão habitual minúscula-depois-maiúscula, acrescente uma divisão entre uma sequência de maiúsculas e uma maiúscula seguida de minúscula. Em expressões regulares são duas passagens: inserir um sublinhado entre ([a-z0-9]) e ([A-Z]), depois entre ([A-Z]+) e ([A-Z][a-z]), e por fim passar a minúsculas. Com essas duas regras, parseHTMLDocument passa a parse_html_document e volta como parseHtmlDocument; getIDFromURL passa a get_id_from_url e volta como getIdFromUrl; exportToPDFFile passa a export_to_pdf_file. As formas kebab são parse-html-document, get-id-from-url e export-to-pdf-file. As fronteiras de palavra sobrevivem.
Repare que a ida e volta continua a não ser a identidade: parseHTMLDocument volta como parseHtmlDocument, com a sigla em caixa de título. Esse é o resultado correto, não um erro residual, e aponta para a correção verdadeira. A secção 5.3 do guia de estilo Java da Google exige exatamente isto no momento da escrita: escreva as siglas como palavras normais, ou seja XmlHttpRequest em vez de XMLHTTPRequest, e o nome torna-se um ponto fixo da conversão. Um nome que sobrevive à sua própria ida e volta é um nome que pode passar sem risco por um gerador de código, um ORM, um serializador e de volta.
Resta um caso que a regra das duas expressões continua a falhar, e vale a pena conhecê-lo porque parece a regra a falhar. As siglas de caixa mista derrotam-na: supportsIPv6 passa a supports_i_pv6, e a forma kebab é supports-i-pv6. A segunda expressão vê o P maiúsculo seguido do v minúsculo e divide ali, que é exatamente o que deve fazer em todo o resto. Nenhuma regra de fronteira que olhe apenas para a caixa das letras pode saber que IPv6 é um único token. Este é o argumento mais forte a favor da regra da Google: escreva supportsIpv6 desde o início e o conversor nunca terá de adivinhar.
Um slug de URL é uma quarta coisa, não kebab-case com passos extra
Um slug parece kebab-case mas tem três obrigações extra que um identificador nunca tem. Tem de sobreviver à passagem a minúsculas, porque os caminhos de URL são comparados pelos servidores distinguindo maiúsculas, mas escritos com descuido pelas pessoas. Tem de sobreviver à remoção de acentos, porque um caminho com carateres acentuados é codificado em percentagens e torna-se ilegível. E tem de caber num orçamento de comprimento, porque os slugs acabam em e-mails, material impresso e barras de endereços onde um caminho de 200 carateres é inutilizável.
O passo de remoção de acentos é onde as implementações ingénuas perdem dados em silêncio. A receita habitual é normalizar para a forma decomposta, apagar as marcas combinantes e depois manter apenas letras, dígitos e hífenes. Aplicada a um título francês funciona: Crème Brûlée & Co. — 2026 Edition fica creme-brulee-co-2026-edition, 28 carateres. Aplicada ao alemão destrói o texto. O título Größe & Maße: der Überblick sai como gro-e-ma-e-der-uberblick, porque o esse alemão não tem decomposição canónica: não é convertido em nada, é simplesmente apagado, tal como qualquer outro caráter que não seja letra latina.
A correção é transliterar antes de normalizar, com um mapa por língua: o esse alemão para ss, as vogais com trema para oe, ae e ue em alemão, o o cortado e o a com anel escandinavos para os equivalentes de duas letras. Com esse passo à frente, o mesmo título alemão dá groesse-masse-der-ueberblick, 28 carateres e de facto legível. Torne o slug resultante imutável assim que publicado, limite-o a uns 60 a 80 carateres cortando numa fronteira de palavra, e nunca o regenere a partir de um título editado sem emitir um redirecionamento a partir do antigo.
Escolher, na prática
Siga o anfitrião, não a sua preferência. Dentro de um ficheiro Python, snake_case, mesmo que o JSON que está a analisar seja camelCase. Dentro de um ficheiro CSS, kebab-case, mesmo que os tokens de design tenham sido escritos em camelCase. Dentro de um esquema PostgreSQL, snake_case, porque o analisador vai passar o seu camelCase a minúsculas de qualquer modo e passará o resto do projeto a escrever aspas duplas.
Converta apenas nas fronteiras, e num único sítio. Se a sua API fala camelCase e a sua base de dados snake_case, ponha uma só camada de correspondência entre ambas em vez de converter avulso em cada chamada, e faça dessa camada o único código que conhece a regra das duas expressões. Escreva as siglas como palavras em todo o lado, para que a conversão seja um ponto fixo e ninguém tenha de voltar a pensar nisso. E quando gerar um slug, trate-o como um identificador publicado a partir do momento em que sai: é a única destas quatro formas que um desconhecido vai colar numa mensagem.
| Ecossistema | Variáveis e funções | Tipos e classes | Constantes | O que o impõe |
|---|---|---|---|---|
| Python (PEP 8) | snake_case | CapWords | UPPER_SNAKE_CASE | Só convenção; os linters avisam, o interpretador aceita tudo |
| Rust | snake_case | UpperCamelCase | SCREAMING_SNAKE_CASE | O compilador: non_snake_case e non_camel_case_types avisam por omissão |
| Go | mixedCaps | MixedCaps | MixedCaps, nunca sublinhados | O compilador: a maiúscula inicial é o que exporta o identificador, logo a caixa é semântica, não estilo |
| Java (Google Java Style) | lowerCamelCase | UpperCamelCase | UPPER_SNAKE_CASE | Convenção, mais a regra explícita de que as siglas se escrevem como palavras: XmlHttpRequest, não XMLHTTPRequest |
| JavaScript e TypeScript | camelCase | PascalCase | UPPER_SNAKE_CASE | Não há guia oficial; a gramática só exclui o hífen, porque é o operador menos |
| CSS e HTML | kebab-case para propriedades, classes e propriedades personalizadas | O CSS não tem tipos definidos pelo utilizador; os nomes de elementos HTML são minúsculos | propriedades personalizadas em kebab-case, prefixadas com dois hífenes | A gramática: um nome de propriedade nunca é uma expressão, logo um hífen no meio não pode ser um menos - e por isso calc() exige espaços à volta dos sinais de menos |
| PostgreSQL | snake_case para tabelas e colunas | snake_case para tipos e domínios | UPPER_SNAKE_CASE apenas por convenção | O analisador: os identificadores sem aspas são convertidos para minúsculas, pelo que um nome de tabela em camelCase fica minúsculo em silêncio a não ser que o coloque entre aspas para sempre |
Perguntas frequentes
- Porque posso escrever background-color em CSS mas não backgroundColor numa folha de estilos?
- Porque os nomes de propriedades CSS são um vocabulário fixo definido pela especificação, e esta escreve-os em kebab-case. Não é que camelCase seja ilegal na gramática: é que backgroundColor não é o nome de propriedade nenhuma, pelo que a declaração é descartada como desconhecida. As grafias em camelCase existem apenas no modelo de objetos CSS, a vista JavaScript de um estilo, onde o hífen seria um sinal de menos. Duas grafias, duas gramáticas, uma propriedade.
- Lê-se melhor camelCase ou snake_case?
- Os trabalhos publicados de rastreio ocular sobre isto são poucos, antigos e contestados, e não sustentam nenhuma afirmação forte em qualquer sentido: alguns estudos acham snake_case marginalmente mais rápido de ler, outros acham que os leitores treinados são mais rápidos no estilo que praticam diariamente. O que não é contestado é o custo da incoerência dentro da mesma base de código. Adote o que o ecossistema exigir, imponha-o com um formatador e gaste o orçamento de discussão em algo que mude comportamentos.
- Como converto em segurança o JSON camelCase de uma API para uma base de dados em snake_case?
- Use a regra de fronteira em duas passagens, aplique-a num único módulo e fixe a correspondência para qualquer nome que contenha uma sigla. A versão pragmática é manter uma tabela de exceções explícita: um dicionário curto com os quinze ou vinte nomes de campo do seu esquema cuja conversão não quer que uma expressão regular decida. Essa tabela custa uma hora a escrever e elimina toda a classe de erro, ao passo que uma correspondência puramente algorítmica acabará por encontrar um nome como supportsIPv6 e produzir algo que nenhum revisor nota até uma consulta não devolver nada.
- Posso usar camelCase para nomes de tabelas e colunas no PostgreSQL?
- Pode, mas apenas colocando o identificador entre aspas em todas as ocorrências, para sempre, em cada consulta, migração, vista e script. O PostgreSQL converte para minúsculas os identificadores sem aspas, pelo que uma tabela criada como userAccounts passa a useraccounts, e uma consulta posterior por userAccounts só a encontra porque também é convertida para useraccounts - até ao dia em que alguém coloca uma delas entre aspas e as duas deixam de corresponder. Aqui snake_case não é preferência de estilo: é a forma que atravessa o analisador intacta.
- Um slug de URL deve ser apenas o título em kebab-case?
- Quase, mas com três acrescentos que o kebab-case sozinho não dá. Passe tudo a minúsculas, porque um caminho que difere apenas na caixa é outro recurso para um servidor mas a mesma coisa para uma pessoa. Translitere antes de remover os acentos, ou carateres como o esse alemão desaparecem por completo em vez de se tornarem ss. E limite o comprimento numa fronteira de palavra, à volta de 60 a 80 carateres, já que o slug será colado em sítios sem espaço. Mais uma regra, alheia à caixa: uma vez publicado, nunca o altere sem um redirecionamento permanente a partir do caminho antigo.
Artigos que podem interessar-lhe
Todos os guias →Ferramentas relacionadas
Fontes
- 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
Detetaste um erro neste artigo?