kebab-case Converter
kebab-case writes hello-world-example. It is the convention for URLs, CSS class names, HTML attributes and file names — anywhere a lowercase, hyphen-separated identifier is expected and underscores would read as a single word.
Conversions
| Input | kebab-case |
|---|---|
hello world example | hello-world-example |
XMLHttpRequest | xml-http-request |
user_first_name | user-first-name |
Total Order Count | total-order-count |
XMLHttpRequest becomes xml-http-request. That is the correct behaviour for a URL or a CSS class, where case sensitivity varies by context and mixed case causes more problems than it solves.The Same Text in Other Cases
| Case | Result |
|---|---|
| camelCase | helloWorldExample |
| PascalCase | HelloWorldExample |
| snake_case | hello_world_example |
| SCREAMING_SNAKE_CASE | HELLO_WORLD_EXAMPLE |
| Train-Case | Hello-World-Example |
| dot.case | hello.world.example |
Where kebab-case Fits
kebab-case is the convention wherever text will appear in a URL, a stylesheet or a filesystem: routes, CSS classes, HTML attributes, npm package names, Docker images and git branches. Search engines treat the hyphen as a word separator, which is the reason URLs use it rather than the underscore.
Converting in Code
``javascript
const kebab = (s) => words(s).map((w) => w.toLowerCase()).join('-');
`
A hyphen is a subtraction operator in most languages, so kebab-case identifiers cannot be used unquoted in code. That is precisely why it belongs in URLs, CSS and filenames and not in variable names.
Where kebab-case Is the Convention
| Context | Example |
|---|---|
| URLs | /blog/how-to-build-an-api |
| CSS classes | .card-header-title |
| HTML attributes | data-user-id |
| Custom elements | |
| npm package names | @scope/my-package |
| File names | user-profile.tsx |
Hyphens are required in some of these, not merely conventional: custom element names must
contain a hyphen, which is how the parser distinguishes them from future built-in elements.For URLs, Google documents hyphens as word separators and underscores as word joiners — so
my_page reads as one token and my-page as two.
Kebab case cannot be used for identifiers in most languages, because the hyphen parses as
subtraction. That is exactly why it is safe in the contexts above: there is no ambiguity with
an operator.
Converting at the API Boundary
The recurring friction is that JavaScript uses camelCase and Python, Ruby, Go and SQL use
snake_case. The fix is to convert in exactly one place — the client that talks to the API —
rather than letting both conventions into the same codebase.
`javascript
const toCamel = (s) => s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
const toSnake = (s) => s.replace(/[A-Z]/g, (c) => '_' + c.toLowerCase());
// Recursively, for a whole payload
const convertKeys = (value, fn) =>
Array.isArray(value)
? value.map((v) => convertKeys(v, fn))
: value && typeof value === 'object'
? Object.fromEntries(Object.entries(value).map(([k, v]) => [fn(k), convertKeys(v, fn)]))
: value;
`
Two things to watch: keys that are user data rather than field names must not be
converted, and the round trip is not always lossless — userID → user_id → userId
changes the original.
Where Each Convention Is Mandatory
Not stylistic — these will break if you deviate:
| Context | Requirement |
|---|---|
| Environment variables | Uppercase with underscores; POSIX reserves lowercase |
| Custom HTML elements | Must contain a hyphen |
| Python modules | Cannot contain hyphens; the import statement will not parse |
| SQL identifiers | Folded to one case unless quoted, so camelCase does not survive |
| React components | Must start uppercase, or JSX treats it as an HTML tag |
Renaming Safely
A find-and-replace across a codebase will hit strings, comments and unrelated identifiers.
Use your language server's rename symbol instead — it understands scope. For a bulk rename
across files, restrict the pattern with word boundaries and review the diff before
committing:
`bash
grep -rn '\buserId\b' src/ # look first
``