The standard answer is str.charAt(0).toUpperCase() + str.slice(1). It works for ASCII, it is the accepted answer on every result page, and it silently corrupts strings that begin with an emoji or certain accented characters. Which matters depends entirely on where your strings come from.
This is a one-line problem with a surprisingly deep tail. The one-liner is correct for most inputs; the interesting part is knowing which inputs it is wrong for, and whether you should be doing this in JavaScript at all.
Table of contents
- The standard approaches
- Where the one-liner breaks
- Locale is not decoration
- Title case, and why it is harder than it looks
- When to use CSS instead
- A version worth keeping
- How this fits the rest of the stack
- FAQ
The standard approaches
// The classic. Safe on empty strings because charAt returns ''.
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);
// Bracket notation -- shorter, but throws on ''
const capitalize2 = str => str[0].toUpperCase() + str.slice(1);
// Regex, replacing the first character
const capitalize3 = str => str.replace(/^./, c => c.toUpperCase());
// Lowercase the rest, for normalising inconsistent input
const capitalizeStrict = str =>
str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
capitalize('hello world'); // 'Hello world'
capitalizeStrict('hELLO wORLD'); // 'Hello world'
Prefer charAt(0) over str[0]. On an empty string, charAt returns an empty string and the function returns ''. Bracket notation returns undefined and throws Cannot read properties of undefined. That is the difference between a harmless no-op and a production error on an empty form field.
The regex version reads nicely and is measurably slower on hot paths. For a render loop over a thousand rows, use the first one; anywhere else it does not matter.
Where the one-liner breaks
JavaScript strings are UTF-16 code units, not characters. charAt(0) returns one code unit, and characters outside the Basic Multilingual Plane occupy two.
const s = '😀 hello';
s.charAt(0); // '\ud83d' -- half an emoji
s.charAt(0).toUpperCase() + s.slice(1); // mangled: '\ud83d😀 hello'
// Correct: split by code point
const capitalizeUnicode = str => {
if (!str) return str;
const [first, ...rest] = str; // iterator yields code points
return first.toUpperCase() + rest.join('');
};
capitalizeUnicode('😀 hello'); // '😀 hello' -- unchanged, correct
capitalizeUnicode('ñandú'); // 'Ñandú'
The spread and destructuring forms use the string iterator, which yields code points rather than code units. That handles emoji and astral-plane characters correctly.
There is a further layer for grapheme clusters — a family emoji or a flag is several code points that render as one glyph. Intl.Segmenter is the correct tool if you genuinely need it:
const capitalizeGrapheme = str => {
if (!str) return str;
const seg = new Intl.Segmenter('en', { granularity: 'grapheme' });
const [first] = seg.segment(str);
return first.segment.toUpperCase() + str.slice(first.segment.length);
};
Use the ASCII version by default and the code-point version for user-supplied text. Display names, comments, and search queries come from users and contain emoji routinely.
Locale is not decoration
toUpperCase() uses locale-independent rules, which are wrong in a small number of languages that matter to the people who speak them.
// Turkish: dotless i uppercases to İ, not I
'istanbul'.charAt(0).toUpperCase(); // 'I' -- wrong
'istanbul'.charAt(0).toLocaleUpperCase('tr-TR'); // 'İ' -- correct
// German ß has no single-character uppercase
'straße'.toUpperCase(); // 'STRASSE'
// Dutch IJ is a digraph; both letters capitalise
// 'ijsselmeer' -> 'IJsselmeer', which no generic function produces
If your application is localised, use toLocaleUpperCase(locale) with the user’s actual locale rather than the runtime default. The Turkish dotless-i case is the canonical example and it produces visibly wrong text for millions of users.
The Dutch digraph case has no general solution, which is a useful reminder: automatic capitalisation is a heuristic, not a transformation with a correct answer in every language.
Title case, and why it is harder than it looks
Capitalising every word is a common follow-on request and a different problem.
// Naive: every word
const titleCase = str =>
str.split(' ')
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
.join(' ');
titleCase('the lord of the rings');
// 'The Lord Of The Rings' -- not correct title case
// Handles hyphens and apostrophes too
const titleCaseWords = str =>
str.replace(/\b\w/g, c => c.toUpperCase());
titleCaseWords("o'brien-smith"); // "O'Brien-Smith"
Real title case leaves articles, conjunctions, and short prepositions lowercase unless they are first or last — The Lord of the Rings. That requires a word list, and the lists differ between style guides.
Note that titleCaseWords capitalises after every apostrophe, which is right for O'Brien and wrong for don't. There is no regex that gets both, which is the point: if you need real title case, use a library that encodes the rules, or store the correctly-cased string.
When to use CSS instead
If you are capitalising purely for display, CSS is usually the better answer. It does not mutate your data, it is locale-aware in the browser, and it costs nothing at runtime.
/* First letter of every word */
.title { text-transform: capitalize; }
/* First letter of the element only -- usually what you want */
.sentence::first-letter { text-transform: uppercase; }
The important distinction: text-transform changes appearance, not content. Copying the text copies the original casing, screen readers announce the original, and your database keeps what the user typed. That is almost always correct — you did not want to overwrite someone’s name because a heading looked better capitalised.
The limitation is that capitalize applies per word with no exceptions, so it produces the same over-capitalised title case as the naive JavaScript version.
Rule of thumb: transform in CSS for presentation, transform in JavaScript only when the capitalised form is the actual value — a generated slug, a normalised key, an API payload.
A version worth keeping
Pulling the considerations together into something you can paste into a utility module:
/**
* Capitalize the first character of a string.
* Unicode-safe, locale-aware, and a no-op on empty input.
*/
export function capitalize(str, locale) {
if (typeof str !== 'string' || str.length === 0) return str;
const [first, ...rest] = str;
const upper = locale
? first.toLocaleUpperCase(locale)
: first.toUpperCase();
return upper + rest.join('');
}
capitalize('hello'); // 'Hello'
capitalize(''); // ''
capitalize(null); // null
capitalize('😀 hi'); // '😀 hi'
capitalize('istanbul', 'tr-TR'); // 'İstanbul'
Returning non-strings unchanged rather than throwing is a deliberate choice — this is a formatting helper, and a null name should render as nothing rather than crash a page.
There is no built-in String.prototype.capitalize in JavaScript and there is unlikely to be one, precisely because the correct behaviour depends on locale and on whether you mean characters, code points, or graphemes. The one-liner is fine; knowing why it is a one-liner and not a standard method is the useful part.
How this fits the rest of the stack
Use charAt(0).toUpperCase() + slice(1) for ASCII, the destructuring form for user text that may contain emoji, and toLocaleUpperCase when your application is localised. For display-only changes, text-transform in CSS leaves your data intact and is nearly always the better call. If you are deploying a frontend and want the build and bandwidth costs as separate figures, the RunxBuild hosting calculator breaks them out.
Useful related references:
- Casing in Python: upper, lower, title, capitalize, and the One That Handles Real Text
- JavaScript replace(): The First-Match Gotcha and What replaceAll Fixed
- JavaScript Sleep: Why There Is No sleep() and What To Use Instead
- Services on RunxBuild
FAQ
How do I capitalize the first letter of a string in JavaScript?
Use str.charAt(0).toUpperCase() + str.slice(1). Prefer charAt over bracket notation, because charAt returns an empty string on empty input while str[0] returns undefined and throws.
Why does capitalizing break strings that start with an emoji?
JavaScript strings are UTF-16 code units and emoji occupy two of them, so charAt(0) returns half a character. Use destructuring — const [first, …rest] = str — which iterates by code point instead.
Is there a built-in capitalize method in JavaScript?
No, and there probably will not be. Correct behaviour depends on locale and on whether you mean code units, code points, or grapheme clusters, so the language leaves the choice to you.
Should I use CSS text-transform or JavaScript?
CSS when the change is presentational — it leaves the underlying data, clipboard content, and screen reader output intact. JavaScript only when the capitalised form is the real value you intend to store or send.
Why does toUpperCase give the wrong result in Turkish?
Turkish has dotted and dotless i as distinct letters, so i uppercases to İ rather than I. Use toLocaleUpperCase with the user’s locale to get the correct result.