As of 2026-10-15
A directory may contain Alice, Alice and 𝐀lice. They look related, but a literal string comparison treats them as different text. Making them searchable under the same query is a product decision. Making them the same account is a much larger decision, and should not happen as an accidental side effect of a search helper.
We build the free fancy text generator at fancyfont.fyi. Its page offers Unicode text styles, copying and a profile preview, and advises keeping searchable words plain. This article looks at a separate developer problem: how an application of your own can search stylized display names without destroying their original spelling.
Keep a second key
Store the display text and the search key separately. The display value is what the person entered. The key expresses your chosen matching policy. The durable account ID should be independent of both.
Unicode provides canonical and compatibility normalization. NFC can bring canonically equivalent forms together while retaining compatibility distinctions. NFKC also folds compatibility distinctions, so fullwidth and mathematical styled letters can become ordinary letters. Unicode cautions that compatibility normalization loses information; see its normalization FAQ and normalization specification.
For a casual directory, that may be a useful search behavior. For mathematical text, a distinct styled letter may carry meaning. Applying NFKC to every stored document would be a much broader change than adding a forgiving name search. Keep the transformation close to the feature that needs it.
| Value | Purpose | Can several records share it? |
|---|---|---|
| Account ID | Stable record identity | No |
| Display name | Presentation chosen by the person | Often yes |
| Normalized search key | Match candidates | Yes, by design |
| Unique handle | Product-defined public identifier | Depends on a separate policy |
Run a collision fixture before building the index
Save the following as names.cjs and run it with Node.js. The sample uses NFKC and lowercase as a deliberately limited directory-search policy. It does not claim to implement full Unicode case folding or an identifier security standard.
const assert = require("node:assert/strict");
function searchKey(value) {
if (typeof value !== "string") throw new TypeError("Expected text");
return value.normalize("NFKC").toLowerCase().normalize("NFKC");
}
const people = [
{ id: "u1", display: "Alice" },
{ id: "u2", display: "Alice" },
{ id: "u3", display: "𝐀lice" },
{ id: "u4", display: "Alicé" }
];
const index = new Map();
for (const person of people) {
const key = searchKey(person.display);
if (!index.has(key)) index.set(key, []);
index.get(key).push(person);
}
assert.equal("A".normalize("NFC"), "A");
assert.equal("A".normalize("NFKC"), "A");
assert.equal("𝐀".normalize("NFKC"), "A");
assert.equal(searchKey("Cafe\u0301"), searchKey("Café"));
assert.notEqual(searchKey("Café"), searchKey("Cafe"));
assert.notEqual(searchKey("Straße"), searchKey("STRASSE"));
assert.notEqual(searchKey("a"), searchKey("\u0430"));
const matches = index.get(searchKey("ALICE")) ?? [];
assert.deepEqual(matches.map(person => person.id), ["u1", "u2", "u3"]);
assert.equal(people[2].display, "𝐀lice");
console.log(matches);
The search for ALICE returns three candidates. That is the intended result: three records share a key. If the index stored a single person per key, each insertion would overwrite the previous match. A normalization improvement would then quietly become a data-loss bug.
Notice what the assertions refuse to collapse. An accented letter is not automatically the unaccented letter. The German sharp s is not converted to ss by this lowercase pipeline. A Cyrillic small a is not the Latin a, despite their appearance in many typefaces. These examples are executable boundaries for this particular helper.
Decide what a collision means in each workflow
In a search box, a collision can produce several results. Show enough context to help the user choose without exposing private details. In an invitation flow, accept a stable identifier or require selection of the intended record. A display-name match alone is weak evidence of the recipient's identity.
In account registration, the same collision may require rejection if the product promises unique handles under this matching rule. That uniqueness must also be enforced where records are stored. Checking in the browser and then inserting without a shared constraint leaves a race between simultaneous registrations.
The key lesson is not to use one helper indiscriminately. Searching, displaying, authenticating and reserving a handle are separate operations. A convenient search result should not authorize access to somebody else's account or silently rename their profile.
Version the policy before changing existing data
Suppose a directory originally indexed exact lowercase text and later adds NFKC. Previously distinct keys can now collide. Rebuild into a new index, inspect the collision groups, and decide how each affected feature should behave before switching traffic.
Keep the old display values during that migration. Normalization is not a reversible encoding: once several inputs have become the same output, the output cannot tell you which original was used. Storing only the normalized value would make it impossible to restore the original presentation accurately.
A migration report can list the policy version and measured collision groups without exposing private names.
Test the UI as well as the helper
The sample verifies string behavior, but a useful search interface also needs tests for empty queries, pasted whitespace, sorting and selection. Decide whether trimming is part of your policy. This helper intentionally does not remove punctuation or trim a person's display name.
Run the fixtures in the runtimes that will produce and consume the key. If your database applies a different collation, compare its uniqueness behavior with the application helper. Compare actual strings, not policy labels.
FAQ
Should I replace display names with NFKC output?
Usually keep the original and derive a separate key. Compatibility normalization can discard distinctions the person intended.
Does normalization stop impersonation?
No. It is a text transformation, not proof of identity or a complete look-alike detection system.
Is lowercase the same as Unicode case folding?
No. The sharp-s assertion demonstrates one difference. Pick a matching policy for the languages and feature you support.
Does the generator provide this search index?
No. The page provides styled text. The index here is an independent example for developers.
The text generator is free, with no sign-up or app required.
Top comments (0)