Styled text is not one uniform Unicode category
What users call “fancy fonts” may be ordinary characters with CSS styling, encoded mathematical alphanumeric symbols, enclosed letters, fullwidth forms, superscripts, subscripts, or strings containing additional combining marks. The first case changes presentation without changing the text. The others can change the actual Unicode scalar sequence. That distinction determines whether normalization preserves or alters an apparently styled label.
For instance, A U+0041 and 𝐀 U+1D400 are two different character identities; the latter belongs to Unicode mathematical alphanumeric symbols and is not simply a bold font selection. Applying font-weight: bold to U+0041 produces no alternate code point, so there is no encoded mathematical style for normalization to fold. Unicode semantics—not a screenshot—determine what normalization does.
NFC/NFD preserve most compatibility styling, but NFKC/NFKD may not
Unicode defines canonical forms NFC and NFD and compatibility forms NFKC and NFKD (UAX #15). NFC/NFD preserve compatibility-only distinctions. NFKC/NFKD recursively apply compatibility decompositions, potentially discarding the original mathematical alphabet, circled enclosure, width, ligature or raised position. A compatibility-folded result is not guaranteed to be the original underlying author's text and normally cannot be inverted.
| Original | NFC | NFKC | What changed? |
|---|---|---|---|
𝐀 U+1D400 | 𝐀 | A | Mathematical bold lost |
𝒜 U+1D49C | 𝒜 | A | Mathematical script lost |
ℬ U+212C | ℬ | B | Historic script BMP symbol folded |
A U+FF21 | A | A | Width lost |
ⓐ U+24D0 | ⓐ | a | Enclosing circle lost |
² U+00B2 | ² | 2 | Raised position lost |
x̲ U+0078 U+0332 | x̲ | x̲ | Combining underline remains in this example |
A critical exception to slogans like “NFKC removes all decoration” is that normalization does not strip every combining mark, invisible character, emoji sequence or variation selector. Some nonspacing marks remain after NFKC. The Unicode Character Database defines exact mapping behavior; a generic plain-text conversion requires a separately specified policy.
Mathematical alphabets may carry distinct meaning
Mathematical bold A is not just a cosmetic variant of Latin A in formal scientific notation. A bold vector, Fraktur algebra or double-struck number set may need to remain distinct even in plain-text interchange. Mathematical Alphanumeric Symbols contain deliberate unassigned holes for earlier BMP letterlike characters; blindly constructing code points via alphabet offsets is not a substitute for official mappings.
In such contexts, NFKC can collapse multiple mathematically distinct variables to the same Latin character, changing the meaning of a formula. Structured MathML and font-specific math rendering are separate considerations. Never use a blanket compatibility normalization pass on equations without a carefully defined mathematical content model.
Exact code points in JavaScript
const list = ["𝐀","𝒜","ℬ","A","ⓐ","²","x\u0332"];
const points = text => [...text].map(c =>
"U+"+c.codePointAt(0).toString(16).toUpperCase());
for (const s of list) {
console.log({before: points(s),nfc:points(s.normalize("NFC")),
nfkc:points(s.normalize("NFKC"))});
}
console.log("𝐀".normalize("NFC")==="𝐀"); // true
console.log("𝐀".normalize("NFKC")==="A"); // true
console.log("x\u0332".normalize("NFKC")==="x\u0332"); // true
JavaScript iteration using [...text] advances by code point, which is essential for supplementary mathematical characters spanning two UTF-16 code units. Even code-point iteration is not grapheme segmentation; an underline sequence can be one perceived grapheme with two scalar values.
Round-trip preservation and the irreversible map
Original source strings should be stored separately from any search-normalized representation when fidelity is part of the product promise. If an editor folds U+1D400 to U+0041 at save time, a later converter cannot determine whether the user typed ordinary A or mathematical bold A. This is true even when the displayed character looks plausible in the saved document.
For copy-paste tests, collect the source scalar stream, clipboard text/plain stream and destination scalar stream separately. A receiving platform may preserve data but use a fallback font, or it may actually rewrite the stream. Distinguish these two outcomes before diagnosing a compatibility failure. The US Fancy Text tool is a concrete source of copyable styled letters for controlled inspection—not a promise that every destination retains them.
Python normalization and decomposition diagnostics
import unicodedata as ud
for ch in ["𝐀","ℬ","A","ⓐ","²"]:
print(ud.name(ch), ud.decomposition(ch),
ud.normalize("NFKC",ch))
assert ud.normalize("NFKC","ℬ") == "B"
assert ud.normalize("NFC","ℬ") == "ℬ"
assert ud.normalize("NFKC","𝐀") == "A"
assert ud.normalize("NFKC","x\u0332") == "x\u0332"
Decomposition strings in UnicodeData use hexadecimal scalar values and may include compatibility tags. They do not mean a browser has selected a different font. Script-specific, presentation-specific and accessible alternatives require independent checks, especially for names, identifiers and scientific text.
Security and accessibility consequences
A visually similar account name may use mathematical styled letters or cross-script confusables. NFKC reduces some compatibility distinctions but does not solve all spoofing or identifier safety issues: UTS #39 and the application's identifier profile must address allowed scripts, context and confusables. For plain-language prose, an all-styled character string can also impose screen-reader or copy-paste burdens compared with semantic formatting.
Use semantic HTML headings and CSS for ordinary emphasis; preserve legitimate encoded symbols when their character identity matters. For case-insensitive or accent-insensitive search, document the full comparison pipeline; normalization, case folding and accent removal are distinct transformations that do not commute in every arbitrary composition.
A reproducible validation matrix
| Fixture | Expected transformation | Failure detected |
|---|---|---|
| U+1D400 (𝐀) | NFKC → U+0041 | Loss of mathematical style |
| U+212C (ℬ) | NFKC → U+0042 | Historical BMP exception |
| U+FF21 (A) | NFC unchanged; NFKC → U+0041 | Width distinction |
| U+0078 U+0332 | Underline remains under NFKC | Invalid “strips all marks” assumption |
| UTF-8 export and reimport | Exact scalar sequence, absent explicit lossy transforms | Encoding mismatch versus normalization |
| Same glyph after font fallback | Inspect code points, not screenshot | Rendering mistaken for data identity |
A conforming normalization library should be verified using the matching version of the Unicode NormalizationTest.txt data file, including boundary cases beyond these illustrative vectors. Products requiring faithful copyable text should treat NFKC as a deliberate, potentially destructive transform.
Standards and implementation references
- UAX #15 — Unicode Normalization Forms
- UAX #44 — UnicodeData compatibility mappings
- Unicode Standard Chapter 22 — Mathematical Alphanumerics
- UTS #39 — Unicode Security Mechanisms
- W3C MathML Core
- MDN — normalize()
Independent technical reference. Normalization and casing rules are algorithmic data transformations, not substitutes for visual or language-specific validation.