DEV Community

Cover image for Unicode Text Styling: Bold, Italic & Strikethrough Without CSS
TechMind
TechMind

Posted on • Originally published at techmind.click

Unicode Text Styling: Bold, Italic & Strikethrough Without CSS

Most developers know HTML bold and italic tags. But what happens when your text lands in an environment that strips all HTML — WhatsApp, Discord DMs, plain text emails, or Instagram captions?
The answer: Unicode mathematical characters.

Why Unicode Styling Works Everywhere

Unicode includes dedicated ranges for bold, italic, bold-italic, and styled variants of Latin letters. These are not formatting instructions — they are different characters that happen to look styled.

Regular:   Hello World
Bold:      𝗛ð—ēð—đð—đ𝗞 𝗊𝗞ð—ŋð—đð—ą
Italic:    𝘏ð˜Ķ𝘭𝘭𝘰 𝘞𝘰ð˜ģ𝘭ð˜Ĩ
Strike:    HĖķeĖķlĖķlĖķoĖķ ĖķWĖķoĖķrĖķlĖķdĖķ
Enter fullscreen mode Exit fullscreen mode

Because they are actual characters, they survive:

  • Copy-paste into any app
  • Plain text environments
  • WhatsApp, Discord, Instagram, Telegram
  • SMS on modern phones
  • LinkedIn posts (which strip HTML completely)

The Unicode Ranges

Mathematical Bold — starts at U+1D400

// Offset for bold lowercase 'a' = U+1D41A
const BOLD_OFFSET_LOWER = 0x1D41A - 'a'.charCodeAt(0);
const BOLD_OFFSET_UPPER = 0x1D400 - 'A'.charCodeAt(0);

function toBold(char) {
  const code = char.charCodeAt(0);
  if (code >= 65 && code <= 90)  // A-Z
    return String.fromCodePoint(code + BOLD_OFFSET_UPPER);
  if (code >= 97 && code <= 122) // a-z
    return String.fromCodePoint(code + BOLD_OFFSET_LOWER);
  return char;
}

const boldText = (str) => [...str].map(toBold).join('');
console.log(boldText("Hello World")); // 𝗛ð—ēð—đð—đ𝗞 𝗊𝗞ð—ŋð—đð—ą
Enter fullscreen mode Exit fullscreen mode

Mathematical Italic — starts at U+1D434

const ITALIC_OFFSET_LOWER = 0x1D44E - 'a'.charCodeAt(0);
const ITALIC_OFFSET_UPPER = 0x1D434 - 'A'.charCodeAt(0);

function toItalic(char) {
  const code = char.charCodeAt(0);
  if (code >= 65 && code <= 90)
    return String.fromCodePoint(code + ITALIC_OFFSET_UPPER);
  if (code >= 97 && code <= 122)
    return String.fromCodePoint(code + ITALIC_OFFSET_LOWER);
  return char;
}
Enter fullscreen mode Exit fullscreen mode

*Strikethrough *— Unicode combining character U+0336

// Add combining strikethrough after each character
const toStrikethrough = (str) =>
  [...str].map(c => c + '\u0336').join('');

console.log(toStrikethrough("Hello")); // HĖķeĖķlĖķlĖķoĖķ
Enter fullscreen mode Exit fullscreen mode

*Underline *— Unicode combining character U+0332

const toUnderline = (str) =>
  [...str].map(c => c + '\u0332').join('');
Enter fullscreen mode Exit fullscreen mode

TypeScript Implementation with Char Maps

For production use, a char map approach is more reliable than offset math — handles edge cases like special chars that don't exist in bold Unicode ranges:

const BOLD_MAP: Record<string, string> = {
  a:'ð—Ū', b:'ð—Ŋ', c:'𝗰', d:'ð—ą', e:'ð—ē', f:'ð—ģ', g:'ð—ī',
  h:'ð—ĩ', i:'ð—ķ', j:'𝗷', k:'ð—ļ', l:'ð—đ', m:'𝗚', n:'ð—ŧ',
  o:'𝗞', p:'ð—―', q:'ð—ū', r:'ð—ŋ', s:'𝘀', t:'𝘁', u:'𝘂',
  v:'𝘃', w:'𝘄', x:'𝘅', y:'𝘆', z:'𝘇',
  A:'𝗔', B:'𝗕', C:'𝗖', // ... etc
};

function applyMap(text: string, map: Record<string, string>): string {
  return [...text].map(c => map[c] ?? c).join('');
}

export const toBold = (text: string) => applyMap(text, BOLD_MAP);
Enter fullscreen mode Exit fullscreen mode

Top comments (0)