Unicode Normalization Converter | Four-Form Side-by-Side Viewer
Paste any string and watch it normalize into NFC, NFD, NFKC, and NFKD at the same time, with per-form character counts, UTF-8 byte counts, and the byte delta against the original. The viewer is built for backend engineers, search-index owners, and i18n hands who need to see exactly what each normalization does to a tricky string before shipping a fix.
💡 About this tool
The most common Unicode bug looks like this in your tracker: "two records have the same name but WHERE name = ? only returns one of them," or "the file uploaded from a Mac shows up in S3 as a different key from the one the iOS app generated," or "this username compares equal in JavaScript but not in Postgres." Nine times out of ten, the underlying cause is one side using a different Unicode normalization form than the other.
This converter takes your input through String.prototype.normalize() in all four official forms — NFC, NFD, NFKC, NFKD — and lays them out next to each other so you can see the decomposition, count the codepoints (the spec says "code units," but we count user-perceived characters here for readability), measure UTF-8 byte size, and watch how the delta changes. The "input form" badge tells you which form your raw input already belongs to, so you immediately know whether you need to convert at all.
NFKC and NFKD are the compatibility decompositions, which means they're willing to throw away typographic distinctions: full-width Latin → half-width, half-width kana → full-width kana, the fi ligature → fi. That's exactly what you want for a search index key, and exactly what you do not want for the canonical storage of someone's name. The visible byte deltas make it obvious when a normalization is destroying information versus merely reshaping it.
🧐 Frequently Asked Questions
Q. NFC or NFD — which one should I pick for stored text? A. NFC, almost always. The W3C's Character Model for the World Wide Web (Charmod-Norm) recommends NFC for HTML, URLs, DOM strings, and any text that crosses a process boundary. NFD is best understood as an internal intermediate representation: useful for stripping diacritics or doing grapheme-cluster work, then re-normalize back to NFC before storage.
Q. Doesn't macOS still store filenames in NFD? A. HFS+ did — it used a specific NFD-with-reserved-range variant. APFS, which is the default on every Apple platform shipped since 2017, no longer normalizes filenames; it stores the bytes exactly as the caller supplied them. So "macOS = NFD" is a half-truth: anything created on modern hardware is whatever-the-app-chose, while files surviving from a Time Machine archive or copied through an old rsync chain may still be NFD. Use this tool to inspect actual bytes rather than guess.
Q. When is NFKC actively the right answer?
A. Building a search key, deduplicating user input where typography doesn't matter, or generating a comparison hash. Wikipedia uses NFKC + casefold() for title lookup, Postgres' citext family combined with collation rules effectively wants normalized input, and most full-text search engines normalize at index time. The rule of thumb: NFKC for the key, original bytes (or NFC) for the value.
Q. What's the difference between a precomposed é and e + combining acute?
A. The precomposed form is one codepoint, U+00E9, encoded as 2 UTF-8 bytes. The decomposed form is two codepoints, U+0065 and U+0301, encoded as 3 UTF-8 bytes. They render identically in any modern font but compare unequal in ===, ==, LIKE, and most languages' default string equality. NFC merges decomposed sequences into precomposed forms where possible; NFD expands precomposed forms into decomposed sequences.
Q. Does normalization change the meaning of the text?
A. Canonical (NFC / NFD) is round-trip-safe by definition: NFC(NFD(s)) === s for any well-formed Unicode s. Compatibility (NFKC / NFKD) is not: ① becomes 1, Ⅻ stays as a Roman numeral character (Unicode treats Roman numerals as having semantic distinction worth preserving), and superscript ² becomes plain 2. Always preview with this tool before applying NFKC to anything you intend to store.
Q. Is String.prototype.normalize reliable across browsers?
A. Yes. It's been in every evergreen browser since 2015 and is backed by ICU on all major engines (V8, JavaScriptCore, SpiderMonkey). The Unicode version supported follows the engine's ICU version, but for normalization that practically only matters when you're dealing with code points added in the last twelve months, which is rare for filename and identifier work.
Q. Why does my emoji string get longer in NFD?
A. Emoji ZWJ sequences (the family emoji, skin-tone variants, regional indicator flags) are sequences, not single code points, so NFC/NFD don't change them at all. What does change length is regional indicator pairs and some math alphanumerics. If you see a big NFKC delta on an emoji string, the culprit is usually mathematical script letters (𝒜, 𝓗) getting flattened to plain ASCII — that is a compatibility decomposition, not a bug.
📚 Fun Facts
The single most consequential Unicode-normalization decision in software history was probably the one IDN (Internationalized Domain Names) had to make. The original IDNA2003 spec mandated NFKC-with-casefolding ("Nameprep") on every label of every domain name, which meant ①.example would happily resolve to 1.example. This turned out to be a security disaster — homograph attacks were trivial — so IDNA2008 replaced Nameprep entirely, mandating that hostnames be in NFC only and rejecting any label containing characters that NFKC would have changed. Today the browser address bar is one of the very few text-handling surfaces in the world that explicitly forbids compatibility decomposition.
A second piece of trivia, less consequential but more fun: the Unicode normalization algorithm's "Canonical Ordering Algorithm" sorts combining marks by their canonical combining class, which is a number assigned to each combining character. The character with the largest CCC value in the standard is U+0345 (COMBINING GREEK YPOGEGRAMMENI), with class 240. The smallest non-zero class belongs to the Hebrew points at class 10. The whole reason this ordering exists is so that a + acute + dot-below normalizes the same way as a + dot-below + acute — without canonical ordering, those two visually-distinct-but-semantically-equal sequences would compare unequal forever. It is, in the very precise sense, a sorting algorithm that exists to make string comparison commutative.