Every identifier you write — a variable, a file, a CSS class, a URL — has to pick a way to glue words together, and the language or platform usually already has an opinion about which way is correct. Getting the case style right is low-effort, high-signal: it tells readers (and tooling) that you know the conventions of the ecosystem you're working in.
This is a reference for the handful of case styles you'll actually meet, where each one is the norm, why mixing them causes real pain, and how to convert cleanly when data crosses a boundary.
The case styles, defined
There are only a few you need to know by name. Here they are with the same three words encoded each way:
| Style | Example | Also called |
|---|---|---|
| camelCase | text userProfileId | lower camel, dromedary case |
| PascalCase | text UserProfileId | upper camel, UpperCamelCase |
| snake_case | text user_profile_id | lower snake |
| SCREAMING_SNAKE_CASE | text USER_PROFILE_ID | upper snake, CONSTANT_CASE |
| kebab-case | text user-profile-id | dash case, lisp case, spinal case |
The mechanics are simple. camelCase lowercases the first word and capitalizes each word after it. PascalCase capitalizes every word including the first. snake_case lowercases everything and joins with underscores. SCREAMING_SNAKE_CASE does the same with uppercase. kebab-case lowercases and joins with hyphens.
The interesting part is not the mechanics — it's that each style has staked out specific territory, and crossing into the wrong territory looks like a bug even when the code runs.
Where each style is conventional
JavaScript and TypeScript: camelCase and PascalCase
In JS/TS, variables, function names, object properties, and method names are camelCase:
jsconst itemCount = 0; function fetchUserProfile(userId) { /* ... */ } const config = { maxRetries: 3, baseUrl: "/api" };
Classes, constructor functions, React/JSX components, and type/interface names are PascalCase:
tsclass HttpClient { /* ... */ } interface UserProfile { id: string; displayName: string; } function UserCard(props) { return <div>{props.name}</div>; }
The React rule is enforced by the renderer, not just convention: a lowercase JSX tag is treated as a DOM element (
<div><UserCard>userCardTrue constants — module-level values that never change and read like configuration — are often SCREAMING_SNAKE_CASE:
jsconst MAX_UPLOAD_BYTES = 5 * 1024 * 1024; const API_BASE_URL = "https://api.example.com";
That convention is a judgment call, not a hard rule. A
constPython: snake_case, PascalCase, SCREAMING_SNAKE_CASE
Python's PEP 8 is unusually prescriptive, which is part of why Python code looks so consistent across projects. Functions, variables, methods, modules, and package names are snake_case:
pythondef load_user_profile(user_id): retry_count = 0 return fetch(user_id)
Classes are PascalCase (PEP 8 calls it CapWords), and module-level constants are SCREAMING_SNAKE_CASE:
pythonMAX_CONNECTIONS = 100 class HttpClient: def send_request(self, payload): ...
Notice the split brain a polyglot developer has to maintain: the same concept ("the maximum number of connections") is
maxConnectionsmax_connectionsCSS: kebab-case
CSS class names, IDs, and custom properties conventionally use kebab-case:
css.card-header { font-weight: 600; } .is-active { opacity: 1; } :root { --primary-color: #2563eb; }
CSS property names themselves are already kebab (
background-colorfont-sizeThe famous wrinkle is the CSS-to-JS boundary. When you set styles in JavaScript, the same property is camelCase, because hyphens aren't legal in JS identifiers:
jselement.style.backgroundColor = "red"; // not background-color element.style.fontSize = "14px";
Custom properties are the exception — you read and write
--primary-colorgetPropertyValue('--primary-color')URLs and slugs: kebab-case
URL paths and slugs use kebab-case by strong convention:
text/blog/naming-conventions-explained /products/wireless-keyboard
Hyphens are the right separator here for readability and because search engines treat hyphens as word boundaries while underscores have historically been treated as word joiners. Spaces would be percent-encoded into
%20Environment variables: SCREAMING_SNAKE_CASE
Shell environment variables are SCREAMING_SNAKE_CASE almost universally:
bashexport DATABASE_URL="postgres://localhost/app" export NODE_ENV=production export MAX_WORKERS=4
This is partly convention and partly POSIX: variable names are restricted to letters, digits, and underscores, and lowercase names are traditionally reserved for shell-local variables, so uppercase distinguishes exported environment variables. Most config loaders (
.envFile and directory names: it depends
File naming is the least settled. Common patterns:
- kebab-case for web assets and many JS/TS projects: ,text
user-profile.tstextdate-utils.js - PascalCase for component files in React projects: text
UserCard.tsx - snake_case for Python modules: text
user_profile.py - A real hazard: case-insensitive filesystems. macOS and Windows often treat andtext
UserCard.tsxas the same file, so an import that works locally can break in CI on Linux. Pick a casing and be consistent to avoid this.textusercard.tsx
Why consistency matters
None of these conventions are enforced by the universe.
user_profile_idTooling expects it. Linters (ESLint's
camelcasenaming-conventionflake8pylintMixed styles read as noise. When a single file has
userIduser_nameUser-emailCasing sometimes is the contract. As shown, React component detection and JSX rely on the leading capital. JSON keys from an API are a contract your client code must match exactly. A field named
created_atcreatedAtIt scales socially. On a team, consistency means a new contributor can predict the name of something they haven't seen yet. That predictability is most of the value.
The pragmatic rule: match the dominant convention of the language and the surrounding code, even if you personally prefer another. Consistency with context beats personal preference every time.
How to convert between cases
Conversion comes up constantly at boundaries — a snake_case API response feeding a camelCase frontend, a title becoming a URL slug, a config key becoming an env var.
The reliable approach is to normalize to a list of lowercase words first, then re-encode into the target style. That avoids brittle find-and-replace.
Here's a compact JavaScript implementation that splits any of the common inputs into words, then formats:
jsfunction splitWords(input) { return input // insert a space between lower/digit and upper: "userId" -> "user Id" .replace(/([a-z0-9])([A-Z])/g, "$1 $2") // handle acronym boundaries: "HTTPServer" -> "HTTP Server" .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2") // treat _ and - as separators .replace(/[_-]+/g, " ") .trim() .toLowerCase() .split(/\s+/); } const toCamel = s => { const w = splitWords(s); return w[0] + w.slice(1).map(cap).join(""); }; const toPascal = s => splitWords(s).map(cap).join(""); const toSnake = s => splitWords(s).join("_"); const toKebab = s => splitWords(s).join("-"); const toScream = s => splitWords(s).join("_").toUpperCase(); function cap(w) { return w.charAt(0).toUpperCase() + w.slice(1); } toCamel("user_profile_id"); // "userProfileId" toKebab("UserProfileId"); // "user-profile-id" toScream("baseUrl"); // "BASE_URL"
A few details that trip people up:
- Acronyms. ,text
HTTPServer,textparseURL— naive splitters mangle these. The second regex above keepstextuserIDtogether. Decide early whether you wanttextHTTPortextparseUrland apply it everywhere.textparseURL - Numbers. Is two words or one? Most splitters keeptext
address2intact; if you needtextaddress2, add a digit-boundary rule.textaddress-2 - Round-tripping isn't guaranteed. Converting to snake givestext
IOError, and back to Pascal givestextio_error. Information about acronym boundaries is lost. If round-trips matter, store the canonical form.textIoError - Don't blindly transform API payloads. Auto-converting every JSON key to camelCase is convenient but can silently break when a key contains data (like a user-supplied map) rather than a fixed field name.
When you just need to convert a value by hand — a single config key, a class name pasted from another language — running it through a case converter is faster and less error-prone than writing the regex, and it shows all the styles at once so you can copy the one you need.
A quick decision guide
- Writing a JS/TS variable or function → camelCase
- Writing a class, type, or React component → PascalCase
- Writing Python anything-not-a-class → snake_case
- Writing a true constant or an env var → SCREAMING_SNAKE_CASE
- Writing a CSS class, a URL path, or a slug → kebab-case
- Unsure → match the file you're already in
The styles aren't arbitrary trivia; they're a shared shorthand that lets code communicate its own intent. Learn the territory each one owns, stay consistent within a context, and convert deliberately at the boundaries — that's the whole discipline.