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:

StyleExampleAlso 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:

js
const itemCount = 0; function fetchUserProfile(userId) { /* ... */ } const config = { maxRetries: 3, baseUrl: "/api" };

Classes, constructor functions, React/JSX components, and type/interface names are PascalCase:

ts
class 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 (

text
<div>
), an uppercase one as a component (
text
<UserCard>
). So
text
userCard
won't render the way you expect — this is a place where casing changes behavior.

True constants — module-level values that never change and read like configuration — are often SCREAMING_SNAKE_CASE:

js
const 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

text
const
that simply holds a value you computed once stays camelCase; reserve the screaming style for genuine, hard-coded constants.

Python: 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:

python
def 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:

python
MAX_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

text
maxConnections
in JS but
text
max_connections
in Python. Neither is more correct — each is correct in its own house.

CSS: 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 (

text
background-color
,
text
font-size
), so kebab class names feel native. There's also a practical reason: CSS is case-insensitive for class names in some contexts, and hyphens sidestep the camelCase ambiguity entirely.

The 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:

js
element.style.backgroundColor = "red"; // not background-color element.style.fontSize = "14px";

Custom properties are the exception — you read and write

text
--primary-color
verbatim via
text
getPropertyValue('--primary-color')
. So a single visual property can wear three names depending on where it lives.

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

text
%20
, which is ugly, so hyphens win. If you're turning a title into a URL fragment, a slug generator handles the lowercasing, hyphenation, and stripping of punctuation and accents in one step.

Environment variables: SCREAMING_SNAKE_CASE

Shell environment variables are SCREAMING_SNAKE_CASE almost universally:

bash
export 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 (

text
.env
files, Docker, CI systems) expect this style.

File 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.ts
    ,
    text
    date-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
    text
    UserCard.tsx
    and
    text
    usercard.tsx
    as the same file, so an import that works locally can break in CI on Linux. Pick a casing and be consistent to avoid this.

Why consistency matters

None of these conventions are enforced by the universe.

text
user_profile_id
works fine as a JavaScript variable. So why bother?

Tooling expects it. Linters (ESLint's

text
camelcase
/
text
naming-convention
, Python's
text
flake8
/
text
pylint
) flag the wrong style. Auto-importers, refactoring tools, and code generators assume the conventional case. Fighting them is a constant low-grade tax.

Mixed styles read as noise. When a single file has

text
userId
,
text
user_name
, and
text
User-email
side by side, every reader pauses to wonder whether the difference is meaningful. Usually it isn't — it's just inconsistency — but they can't know that without checking. Consistent casing lets the style carry information: PascalCase means "this is a type or component," SCREAMING_SNAKE means "this is a constant."

Casing 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

text
created_at
does not magically become
text
createdAt
on the wire.

It 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:

js
function 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
    ,
    text
    parseURL
    ,
    text
    userID
    — naive splitters mangle these. The second regex above keeps
    text
    HTTP
    together. Decide early whether you want
    text
    parseUrl
    or
    text
    parseURL
    and apply it everywhere.
  • Numbers. Is
    text
    address2
    two words or one? Most splitters keep
    text
    address2
    intact; if you need
    text
    address-2
    , add a digit-boundary rule.
  • Round-tripping isn't guaranteed. Converting
    text
    IOError
    to snake gives
    text
    io_error
    , and back to Pascal gives
    text
    IoError
    . Information about acronym boundaries is lost. If round-trips matter, store the canonical form.
  • 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.