How Fancy Text Generators Work: Unicode Explained
ู ูุฎุต
Pixelโs announcement explains how fancy text generators replace ordinary letters with distinct Unicode characters rather than applying fonts. It explores the Mathematical Alphanumeric Symbols block, shows JavaScript conversion examples, and highlights edge cases that matter when building copyable text tools.
DEV Community
How Fancy Text Generators Actually Work (Itโs Just Unicode)
You've seen it in Instagram bios, Twitter names and Discord servers: text like ๐๐จ๐ฅ๐, ๐ข๐ฌ๐ป๐ฒ๐น๐ฝ or ๐พ๐ ๐ฅ๐๐๐ in apps that don't let you pick a font at all. So how does it work? Short answer: it isn't a font. Fancy text generators swap each normal letter for a different Unicode character that happens to look styled. In this post I'll show you exactly which characters they use, the few lines of JavaScript that do the conversion, and the edge cases that broke my own generator when I built one. It's Not a Font, It's a Different Character A fancy text generator doesn't change how a letter looks. It replaces the letter with a completely separate character. A font is a visual skin applied to the same character, but ๐ and A are two different characters with two different code points. You can prove it in your browser console: console.log("๐" === "A"); // false
console.log("๐".codePointAt(0).toString(16)); // "1d400"
U+1D400 is officially named MATHEMATICAL BOLD CAPITAL A. Because it's a real character and not styling, it survives copy and paste. That's why it works in places with no formatting options: an Instagram bio, a username field, a plain . Where These Characters Come From: The Mathematical Alphanumeric Symbols Block Most "fonts" in a fancy text generator come from one Unicode block called Mathematical Alphanumeric Symbols, which runs from U+1D400 to U+1D7FF. It was added in Unicode 3.1 (2001) for mathematicians, not for social media. In math notation, a bold x, an italic x and a script ๐ can mean three different things, so each one needed its own character to keep that meaning in plain text. The useful part for developers is how the block is laid out. Each style is a neat run of 26 capitals followed by 26 lowercase letters: Style Example Capital A Small a Bold ๐๐ U+1D400 U+1D41A Italic ๐ด๐ U+1D434 U+1D44E Script ๐๐ถ U+1D49C U+1D4B6 Bold Script ๐๐ช U+1D4D0 U+1D4EA Fraktur ๐๐ U+1D504 U+1D51E Double-struck ๐ธ๐ U+1D538 U+1D552 Sans-serif ๐ ๐บ U+1D5A0 U+1D5BA Monospace ๐ฐ๐ U+1D670 U+1D68A The block also holds bold, double-struck, sans-serif and monospace digits. Bold ๐ starts at U+1D7CE. Because the letters sit in alphabetical order, converting text is just arithmetic. The Character Offset Trick in JavaScript To convert a letter, find its position in the alphabet (A = 0, B = 1 and so on) and add that number to the style's starting code point. That's the whole trick. Here's the simplest version, a bold converter: function toBold(text) {
return [...text].map((ch) => {
const code = ch.codePointAt(0);
if (code >= 65 && code <= 90) return String.fromCodePoint(0x1d400 + (code - 65)); // A-Z
if (code >= 97 && code <= 122) return String.fromCodePoint(0x1d41a + (code - 97)); // a-z
return ch; // leave spaces, numbers and punctuation alone
}).join("");
}
console.log(toBold("Hello, World!")); // ๐๐๐ฅ๐ฅ๐จ, ๐๐จ๐ซ๐ฅ๐!
Two details matter here: String.fromCodePoint(), not fromCharCode(). These characters are above U+FFFF, so fromCharCode() can't create them in one call. [...text] instead of text.split(""). Spreading a string iterates by code point, so it won't cut a fancy character in half. More on that later. To support every style, store the starting points in a map and reuse one function: const STYLES = {
bold: { upper: 0x1d400, lower: 0x1d41a },
italic: { upper: 0x1d434, lower: 0x1d44e },
script: { upper: 0x1d49c, lower: 0x1d4b6 },
boldScript: { upper: 0x1d4d0, lower: 0x1d4ea },
fraktur: { upper: 0x1d504, lower: 0x1d51e },
doubleStruck: { upper: 0x1d538, lower: 0x1d552 },
sans: { upper: 0x1d5a0, lower: 0x1d5ba },
monospace: { upper: 0x1d670, lower: 0x1d68a },
};
function stylize(text, style) {
const { upper, lower } = STYLES[style];
return [...text].map((ch) => {
const code = ch.codePointAt(0);
if (code >= 65 && code <= 90) return String.fromCodePoint(upper + code - 65);
if (code >= 97 &...

Powered by Readybot.io