DEV Community

BeGoodTool.com
BeGoodTool.com

Posted on

Turns out "fancy Instagram fonts" are just Unicode โ€” here's how I built a generator for it

A while back someone half-jokingly asked if I could make a tool that turns "hello" into "๐“ฑ๐“ฎ๐“ต๐“ต๐“ธ" for their Instagram bio. My first instinct was "that's just a custom font" โ€” but it's not. You can't swap fonts inside a plain text field on Instagram, TikTok, or Discord. So how do all those stylized bios actually work?

Turns out the trick has nothing to do with fonts. It's Unicode.

The block nobody tells you about: Mathematical Alphanumeric Symbols

Unicode reserves a whole range (U+1D400โ€“U+1D7FF) called Mathematical Alphanumeric Symbols. It exists so mathematicians can write ๐€ (bold), ๐ด (italic), ๐’œ (script), ๐”„ (fraktur) as genuinely distinct characters instead of relying on rich-text formatting. Nothing stops you from typing them into a regular text field, though โ€” they render exactly like normal text, no font, no app support required.

So a "fancy font converter" is really just a character-substitution cipher against a lookup table. For every style I built a 62-character string covering Aโ€“Z, aโ€“z, 0โ€“9, mapped 1:1 onto the matching styled code points:

const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

const fontsMapping = [
  { id: "serifBold", map: "๐€๐๐‚๐ƒ๐„๐…๐†๐‡๐ˆ๐‰๐Š๐‹๐Œ๐๐Ž๐๐๐‘๐’๐“๐”๐•๐–๐—๐˜๐™๐š๐›๐œ๐๐ž๐Ÿ๐ ๐ก๐ข๐ฃ๐ค๐ฅ๐ฆ๐ง๐จ๐ฉ๐ช๐ซ๐ฌ๐ญ๐ฎ๐ฏ๐ฐ๐ฑ๐ฒ๐ณ๐ŸŽ๐Ÿ๐Ÿ๐Ÿ‘๐Ÿ’๐Ÿ“๐Ÿ”๐Ÿ•๐Ÿ–๐Ÿ—" },
  { id: "fraktur",    map: "๐”„๐”…โ„ญ๐”‡๐”ˆ๐”‰๐”Šโ„Œโ„‘๐”๐”Ž๐”๐”๐”‘๐”’๐”“๐””โ„œ๐”–๐”—๐”˜๐”™๐”š๐”›๐”œโ„จ๐”ž๐”Ÿ๐” ๐”ก๐”ข๐”ฃ๐”ค๐”ฅ๐”ฆ๐”ง๐”จ๐”ฉ๐”ช๐”ซ๐”ฌ๐”ญ๐”ฎ๐”ฏ๐”ฐ๐”ฑ๐”ฒ๐”ณ๐”ด๐”ต๐”ถ๐”ท0123456789" },
  // ...30 more of these
];

function convertString(str, font) {
  const mapArray = Array.from(font.map); // Array.from, not split() โ€” more on that below
  let result = "";
  for (const char of str) {
    const index = chars.indexOf(char);
    result += index !== -1 && mapArray[index] ? mapArray[index] : char;
  }
  return result;
}
Enter fullscreen mode Exit fullscreen mode

Two things bit me while building this:

1. Array.from() instead of .split(''). A lot of these code points live above U+FFFF, so they're encoded as surrogate pairs in JS strings. map.split('') slices a pair in half and hands you back mangled characters. Array.from() iterates by code point, which is what you actually want here.

2. Not every style has a full alphabet. Some Unicode "fonts" โ€” small caps, superscript, the currency-symbol style โ€” only exist for a subset of letters. There's no true Unicode superscript "Q," for instance, so I substituted the closest visual look-alike from a completely different block. For those styles it's not really a clean 1:1 cipher, it's closer to visual forgery.

The weird one: Zalgo text

The glitch/cursed-text effect (tฬธhฬถiฬตsฬด ฬถsฬดoฬทrฬถtฬต ฬถoฬดfฬต ฬธtฬถhฬทiฬดnฬถgฬธ) isn't from the same character block at all โ€” it's combining diacritical marks. Normally these stack a single accent on top of a letter (the mark that turns e into รฉ). Nothing stops you from stacking dozens of them on the same character:

const zalgoUp   = ["ฬ", "ฬŽ", "ฬ„", /* ...36 more */];
const zalgoMid  = ["ฬ•", "ฬ›", "อ€", /* ... */];
const zalgoDown = ["ฬ–", "ฬ—", "ฬ˜", /* ... */];

for (const char of str) {
  result += char;
  const numUp = Math.floor(Math.random() * 2) + 1;
  for (let j = 0; j < numUp; j++) {
    result += zalgoUp[Math.floor(Math.random() * zalgoUp.length)];
  }
  // same idea for the mid and down mark sets
}
Enter fullscreen mode Exit fullscreen mode

Randomly stack 1โ€“2 marks above, 0โ€“1 in the middle, and 1โ€“2 below every character, and you get the "reality is glitching" look. It's not elegant โ€” it's controlled chaos โ€” but the randomness is exactly what sells the effect.

The other decorations are combining marks too

Strikethrough, underline, and overline use the same combining-character trick, just with a single mark appended after each letter instead of a lookup substitution:

{ id: "strikethrough", decorator: "ฬถ" },
{ id: "underline",     decorator: "ฬฒ" },
Enter fullscreen mode Exit fullscreen mode

"h" + "ฬถ" renders as hฬถ. No lookup table needed โ€” just append and skip spaces, since a combining mark attached to a space usually renders oddly or gets silently dropped by the renderer.

Why this breaks more than it should

Once you know the trick, the limitations make sense:

  • These are still plain Unicode characters to a screen reader, not letters โ€” text-to-speech often can't pronounce them correctly.
  • Search engines and in-app search generally won't match "๐“ฑ๐“ฎ๐“ต๐“ต๐“ธ" against "hello," so you lose searchability the moment you use it.
  • Devices or fonts missing glyphs for the rarer blocks fall back to the classic empty box (โ˜’).

None of that stops it from being fun โ€” but it's worth knowing you're trading "cool font" for "no longer quite text" in the eyes of most software that touches it.

I turned the whole thing (30+ styles plus the Zalgo generator) into a small free tool if you want to play with it without building your own lookup table: Instagram Font Generator. No sign-up, it's a static page.


Available in other languages

Top comments (0)