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)