ASCII trained everyone to think of case as a perfect two-way mapping: A–Z and a–z pair up exactly, so uppercasing and lowercasing look reversible. Unicode breaks that model on purpose. The standard's SpecialCasing.txt defines mappings that are one-to-many, locale-dependent, and context-sensitive, and JavaScript's toUpperCase() and toLowerCase() implement them faithfully — so the surprises land in production code.
The German sharp s is the classic one-to-many case: 'ß'.toUpperCase() returns 'SS', so the output is longer than the input. A capital ẞ (U+1E9E) was added to Unicode in 2008 and has been officially allowed in German orthography since 2017, but the default mapping still produces SS. That means 'straße'.toUpperCase().toLowerCase() comes back as 'strasse' — the round trip destroys the original spelling.
Turkish and Azerbaijani distinguish a dotted and a dotless i. Under the Turkish locale, 'i'.toLocaleUpperCase('tr') yields 'İ' (U+0130) and 'I'.toLocaleLowerCase('tr') yields 'ı' (U+0131). Meanwhile 'İ'.toLowerCase() in the default locale returns 'i' plus a combining dot above (U+0307), so its .length is 2. This is the Turkish-I bug family: software that lowercases config keys or protocol tokens breaks the moment it runs on a machine whose default locale is Turkish.
Greek adds context sensitivity: capital Σ lowercases to σ inside a word but to ς at the end, per Unicode's Final_Sigma rule, and JavaScript applies it. The practical rules that follow: never assume lower(upper(x)) === x, never assume length is preserved across a case transform, and never use plain case conversion as a security-relevant canonicalization step.
'ß'.toUpperCase() // 'SS' — one char in, two out
'straße'.toUpperCase()
.toLowerCase() // 'strasse' — ß never comes back
'i'.toLocaleUpperCase('tr') // 'İ' (U+0130, dotted capital I)
'I'.toLocaleLowerCase('tr') // 'ı' (U+0131, dotless i)
'İ'.toLowerCase().length // 2 — 'i' + combining dot (U+0307)
'ΣΟΦΟΣ'.toLowerCase() // 'σοφος' — medial σ, final ς
Where Does One Word End? Boundary Detection and Acronym Rules
Every case converter has to answer one question before anything else: what are the words? Splitting on underscores, hyphens, and spaces is trivial; the hard part is camelCase input containing consecutive capitals. A naive rule that starts a new word at every uppercase letter shreds parseHTMLDocument into parse / H / T / M / L / Document. Solid tokenizers use two rules instead: a lowercase-to-uppercase transition opens a new word, and within an uppercase run, the final capital that is followed by a lowercase letter opens a new word — producing parse / HTML / Document.
Digits are ambiguous by nature. Should v2Response split as v2 / Response or v / 2 / Response? Is base64Encode base64 / Encode or base / 64 / Encode? There is no universal answer. Most converters attach digits to the preceding word, which is exactly why user_id_2 and user_id2 tend to collapse into the same camelCase output.
Style guides took explicit positions on acronyms because mechanical rules disagree. Google's Java Style Guide treats acronyms as ordinary words: XmlHttpRequest, never XMLHTTPRequest; newCustomerId, not newCustomerID. The Swift API Design Guidelines require acronyms to be uniformly cased by position — utf8Bytes at the start of a name, userSMTPServer in the middle. Go went the other way: golint shipped a hardcoded list of initialisms (ID, URL, HTTP, API, JSON, and roughly forty more) that keep canonical casing, so idiomatic Go writes userID and ServeHTTP. The same phrase therefore has a different correct camelCase in each ecosystem; a converter can only pick one policy and apply it consistently.
Case Conversion Is Lossy: Round Trips Do Not Come Back
Converting between conventions destroys information, and it is worth knowing exactly which information. Most converters normalize input to a canonical word list — lowercase words, separators discarded — and re-join it in the target style. Any two inputs that produce the same word list become permanently indistinguishable.
Capitalization inside a word is the first casualty. HTML_parser becomes htmlParser in camelCase; convert back and you get html_parser — nothing records that HTML was originally uppercase. Digit boundaries are the second: user_id_2 becomes userId2, and the reverse conversion emits user_id2, because a digit carries no case information to mark where the separator used to be. Leading separators are the third: _private, the Python convention for internal members, camelCases to private — the semantic prefix is simply gone, as are the double underscores that trigger Python's name mangling. Consecutive separators (a__b) collapse to one.
The engineering consequence: never re-derive names. Code generators that map identifiers across layers — ORM columns to fields, protobuf messages to JSON — store the original name alongside the derived one instead of trusting a round trip. Protocol Buffers does exactly this: every field carries an explicit json_name, so a lossy conversion can never silently rename part of your API.
user_id_2 → camelCase → userId2
userId2 → snake_case → user_id2 // '_2' boundary lost
HTML_parser → camelCase → htmlParser
htmlParser → snake_case → html_parser // HTML capitals lost
_private → camelCase → private // leading '_' lost
a__b → camelCase → aB // double separator collapsed
Contexts Where Case Is Enforced by Spec, Not by Taste
Naming conventions are usually social contracts, but several systems make case a hard rule. HTTP header field names are case-insensitive per RFC 9110, and HTTP/2 and HTTP/3 go further — they require field names to be lowercase on the wire, which is why modern proxies show content-type, not Content-Type. Environment variables are case-sensitive on POSIX systems (PATH and path can coexist) and uppercase by convention; on Windows they are case-insensitive, an interop wrinkle for cross-platform scripts.
CSS custom properties are case-sensitive (--Main-Color and --main-color are different variables) even though HTML attribute and tag names are not. Custom element names must be lowercase and contain a hyphen (my-widget) — that is HTML spec, not style. npm has rejected uppercase letters in new package names since 2017; older mixed-case packages like JSONStream are grandfathered in.
Databases fold unquoted identifiers in opposite directions: PostgreSQL lowercases them, Oracle uppercases them — each citing the SQL standard. Create a table as "UserAccounts" with quotes in Postgres and every future query must quote it, forever; port that schema to Oracle and the folding direction flips.
Filesystems are the last trap. APFS on macOS and NTFS on Windows are case-insensitive by default (though case-preserving), while Linux ext4 is case-sensitive. An import './Button' that resolves against button.tsx works on every developer laptop and fails only in Linux CI — one of the most reliable sources of "works on my machine".
Refactoring Across Conventions: A Practical Workflow
Renaming a domain concept means chasing it through every convention at once: userAccount in TypeScript, user_account in SQL and Python, user-account in CSS classes and URLs, USER_ACCOUNT in env vars, UserAccount in class names. A search for one variant finds a fraction of the usages. The reliable workflow is to generate all variants first (this tool does that in one paste), then search with an alternation — ripgrep handles it in a single pass — and review hits before replacing anything.
IDE support makes the replacement itself safe. JetBrains' Replace with "Preserve case" and VS Code's preserve-case toggle (the AB button in the replace box) map userAccount to memberProfile and UserAccount to MemberProfile in one operation, so five separate replacements are unnecessary.
At API boundaries, do not convert by hand at all. Wire formats are commonly snake_case (Stripe, most Python and Ruby backends) while JS clients expect camelCase; convert once at the serialization layer with a declared policy — serde's rename_all = "camelCase" in Rust, Jackson's PropertyNamingStrategies.SNAKE_CASE in Java, camelcase-keys in Node. One caveat: deep key conversion must skip maps whose keys are data rather than identifiers — case-converting a dictionary keyed by external IDs silently corrupts it.
# Find every variant of "user account" in one ripgrep pass
rg -n '(userAccount|UserAccount|user_account|user-account|USER_ACCOUNT)'
# Rust: convert once at the serialization boundary
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct UserAccount {
user_id: u64, // wire: "userId"
display_name: String, // wire: "displayName"
}
Last updated:
About this tool
A case converter transforms text between programming naming conventions: camelCase, PascalCase, snake_case, kebab-case, CONSTANT_CASE, and more. Different languages and frameworks expect different conventions — JavaScript favors camelCase, Python favors snake_case, CSS classes use kebab-case, environment variables use CONSTANT_CASE — and converting by hand is tedious and error-prone.
How to use
Type or paste the text you want to convert into the input box.
All conversion variants render simultaneously in the results grid.
Find the case style you need (camelCase, kebab-case, etc.) in the appropriate card.
Click Copy on that card to put the result on your clipboard.
Paste it into your IDE, terminal, config file, or commit message.
Common use cases
Renaming a database column (snake_case) into a TypeScript field (camelCase).
Generating CSS class names (kebab-case) from a design token name.
Converting JSON keys to match a different language convention during API integration.
Producing CONSTANT_CASE keys for .env files from a human-readable label.
Quickly producing a PascalCase class name from a feature description in a ticket.
Normalizing inconsistent variable names during a refactor.
Frequently asked questions
Q. What is the difference between camelCase and PascalCase?
A. Both join words without separators, but camelCase keeps the first letter lowercase (myVariable) while PascalCase capitalizes it (MyClass). PascalCase is conventionally reserved for classes, types, and components.
Q. How do you handle acronyms like URL or ID?
A. Modern style guides (Google, Microsoft) treat acronyms as single words: parseUrl, userId. The converter follows that rule for predictable round-trip conversions.
Q. Will the tool guess word boundaries from a CONSTANT_CASE input?
A. Yes. It splits on underscores, hyphens, spaces, and case transitions, so HELLO_WORLD, helloWorld, and hello-world all produce the same canonical word list before re-joining.
Q. Does conversion work with non-English characters?
A. Yes for case-insensitive scripts. Languages without case (Korean, Japanese, Chinese) keep their characters intact and only the separators are adjusted.