<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Iris Khan</title>
    <description>The latest articles on DEV Community by Iris Khan (@iris_khan_dev).</description>
    <link>https://dev.to/iris_khan_dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4053248%2Fff92389d-f06c-4238-9b58-a1f2c2714cdf.jpg</url>
      <title>DEV Community: Iris Khan</title>
      <link>https://dev.to/iris_khan_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iris_khan_dev"/>
    <language>en</language>
    <item>
      <title>Cool Fonts Are Not Fonts: The Complete Guide to Unicode Text Styles</title>
      <dc:creator>Iris Khan</dc:creator>
      <pubDate>Tue, 27 Oct 2026 13:58:50 +0000</pubDate>
      <link>https://dev.to/iris_khan_dev/cool-fonts-are-not-fonts-the-complete-guide-to-unicode-text-styles-23jj</link>
      <guid>https://dev.to/iris_khan_dev/cool-fonts-are-not-fonts-the-complete-guide-to-unicode-text-styles-23jj</guid>
      <description>&lt;p&gt;The first time I built a "font generator," I felt like a fraud. There was no font anywhere in my code. No font file, no CSS, no typeface loading in the background. There was just a table: a list of ordinary letters on one side, and a list of strange, beautiful lookalike characters on the other. When someone typed their name and hit copy, my code quietly swapped every letter for its lookalike, and the result pasted neatly into an Instagram bio as if real styling had happened.&lt;/p&gt;

&lt;p&gt;That swap is the entire technology behind &lt;a href="https://fonti.cool" rel="noopener noreferrer"&gt;cool fonts&lt;/a&gt;, and once you see it, you cannot unsee it. Every cursive bio, every gothic gaming name, every circled username you have ever admired is built from characters that Unicode created for completely different jobs: mathematics, television broadcasting, phonetic transcription, and lining up text next to Chinese characters. This guide is everything I have learned about those characters while building with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What cool fonts really are
&lt;/h2&gt;

&lt;p&gt;A font is a set of drawings. It tells a device how to paint a character: the letter A stays the letter A, and the font decides whether it gets serifs, curves, or sharp gothic corners. Change the font and the text underneath does not change at all, which is why fonts cannot travel. Paste styled text from a word processor into a plain bio field and the styling falls off, because only the characters made the trip.&lt;/p&gt;

&lt;p&gt;Cool fonts work the opposite way. The characters themselves change. When a generator turns "Cool" into 𝐂𝐨𝐨𝐥, it has not bolded anything. It has replaced C (U+0043) with 𝐂 (U+1D402, MATHEMATICAL BOLD CAPITAL C), and done the same for every other letter. The output is a different string that happens to look like your word wearing a costume.&lt;/p&gt;

&lt;p&gt;This single fact explains everything else in this guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The style survives copy and paste, because the style is the text.&lt;/li&gt;
&lt;li&gt;No app needs to support fonts for it to work. Every modern app already renders Unicode.&lt;/li&gt;
&lt;li&gt;Nothing can unstyle it. There is no formatting layer to remove, only different characters to replace.&lt;/li&gt;
&lt;li&gt;Machines that read text literally, like search engines, screen readers, and databases, see the costume, not your word.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where the styles actually come from
&lt;/h2&gt;

&lt;p&gt;None of these characters were designed for bios. Each family of styles was borrowed from a serious job, and knowing the original job predicts how the style behaves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mathematics block.&lt;/strong&gt; Most of the big styles, bold, italic, script, Fraktur, double struck, monospace, and the sans serif family, live in one Unicode block called Mathematical Alphanumeric Symbols, U+1D400 to U+1D7FF. Unicode added it in version 3.1, back in 2001, so mathematicians could treat styled letters as different symbols. In mathematics, an italic 𝐴 and a bold 𝐀 in the same formula can mean two different things, so they had to be different characters, not one character in two fonts. The block holds 997 assigned characters, and Unicode's own documentation says they are meant for mathematical notation, expressly recommending against using them in ordinary text as a substitute for real formatting. The whole cool fonts industry is, technically, a misuse the standard warned about in advance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The borrowed letters.&lt;/strong&gt; The math block has holes, and the holes are my favorite detail in all of Unicode. Some styled letters were never added to the block because they already existed in an older block called Letterlike Symbols. So the script capital H in your name is ℋ (U+210B, SCRIPT CAPITAL H), the script e is ℯ (U+212F), and the italic small h is ℎ (U+210E), which is officially the Planck constant from quantum physics. In total, 24 characters that belong to the math alphabets actually live in the Letterlike block, and their math block slots are reserved forever. Every good generator stitches the two blocks together so quietly that you never notice. Every bad generator leaves boxes where those letters should be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The television letters.&lt;/strong&gt; Circled and squared letters come from Enclosed Alphanumerics (U+2460 to U+24FF) and its supplement. Part of that family was encoded for compatibility with ARIB STD B24, the Japanese digital broadcasting standard, whose symbols labeled broadcast services and program types on screen. The humble bubble letter in a cute username is, by ancestry, Japanese TV infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The wide letters.&lt;/strong&gt; Fullwidth text (Ｃｏｏｌ) comes from the Halfwidth and Fullwidth Forms block, created so Latin letters could share the grid width of Chinese, Japanese, and Korean characters. The vaporwave look is a side effect of lining text up with kanji.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The small letters.&lt;/strong&gt; Small caps are not really small capitals. They are lookalikes assembled from the IPA and phonetic extension blocks, characters linguists use to transcribe speech sounds. Unicode never promised a full set of them, and there is famously no true small capital X, so generators leave a plain x in its place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The marks.&lt;/strong&gt; Strikethrough, underline, and zalgo effects use a different trick entirely: combining marks. These are characters with no width of their own, from blocks like Combining Diacritical Marks (U+0300 to U+036F), that attach to the character before them. One mark gives you é. One overlay mark per letter gives you C̶o̶o̶l̶. Dozens of marks stacked on every letter gives you zalgo, the horror effect named after a 2004 internet meme, which works because Unicode never set a limit on how many marks may pile onto one character.&lt;/p&gt;

&lt;h2&gt;
  
  
  The styles chart
&lt;/h2&gt;

&lt;p&gt;Here is every major style from the verified mapping table my own tools use, one phrase per row. Notice the script and Fraktur rows quietly borrowing their capital C, F, and H from the Letterlike block.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Style&lt;/th&gt;
&lt;th&gt;Specimen&lt;/th&gt;
&lt;th&gt;Where it lives&lt;/th&gt;
&lt;th&gt;Best first use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bold&lt;/td&gt;
&lt;td&gt;𝐂𝐨𝐨𝐥 𝐅𝐨𝐧𝐭𝐬&lt;/td&gt;
&lt;td&gt;Math block&lt;/td&gt;
&lt;td&gt;Bio headings, short emphasis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Italic&lt;/td&gt;
&lt;td&gt;𝐶𝑜𝑜𝑙 𝐹𝑜𝑛𝑡𝑠&lt;/td&gt;
&lt;td&gt;Math block&lt;/td&gt;
&lt;td&gt;Quotes, soft emphasis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bold italic&lt;/td&gt;
&lt;td&gt;𝑪𝒐𝒐𝒍 𝑭𝒐𝒏𝒕𝒔&lt;/td&gt;
&lt;td&gt;Math block&lt;/td&gt;
&lt;td&gt;Names that must stand out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script&lt;/td&gt;
&lt;td&gt;𝒞ℴℴ𝓁 ℱℴ𝓃𝓉𝓈&lt;/td&gt;
&lt;td&gt;Math block + Letterlike&lt;/td&gt;
&lt;td&gt;Weddings, beauty, signatures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bold script&lt;/td&gt;
&lt;td&gt;𝓒𝓸𝓸𝓵 𝓕𝓸𝓷𝓽𝓼&lt;/td&gt;
&lt;td&gt;Math block&lt;/td&gt;
&lt;td&gt;Cursive names with better coverage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fraktur&lt;/td&gt;
&lt;td&gt;ℭ𝔬𝔬𝔩 𝔉𝔬𝔫𝔱𝔰&lt;/td&gt;
&lt;td&gt;Math block + Letterlike&lt;/td&gt;
&lt;td&gt;Gothic and gaming names&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bold Fraktur&lt;/td&gt;
&lt;td&gt;𝕮𝖔𝖔𝖑 𝕱𝖔𝖓𝖙𝖘&lt;/td&gt;
&lt;td&gt;Math block&lt;/td&gt;
&lt;td&gt;Dark aesthetics, metal vibes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Double struck&lt;/td&gt;
&lt;td&gt;ℂ𝕠𝕠𝕝 𝔽𝕠𝕟𝕥𝕤&lt;/td&gt;
&lt;td&gt;Math block + Letterlike&lt;/td&gt;
&lt;td&gt;Techy names, outline look&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sans&lt;/td&gt;
&lt;td&gt;𝖢𝗈𝗈𝗅 𝖥𝗈𝗇𝗍𝗌&lt;/td&gt;
&lt;td&gt;Math block&lt;/td&gt;
&lt;td&gt;Clean, modern profiles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sans bold&lt;/td&gt;
&lt;td&gt;𝗖𝗼𝗼𝗹 𝗙𝗼𝗻𝘁𝘀&lt;/td&gt;
&lt;td&gt;Math block&lt;/td&gt;
&lt;td&gt;The safest strong style&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sans italic&lt;/td&gt;
&lt;td&gt;𝘊𝘰𝘰𝘭 𝘍𝘰𝘯𝘵𝘴&lt;/td&gt;
&lt;td&gt;Math block&lt;/td&gt;
&lt;td&gt;Minimal captions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sans bold italic&lt;/td&gt;
&lt;td&gt;𝘾𝙤𝙤𝙡 𝙁𝙤𝙣𝙩𝙨&lt;/td&gt;
&lt;td&gt;Math block&lt;/td&gt;
&lt;td&gt;Bold with a lean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monospace&lt;/td&gt;
&lt;td&gt;𝙲𝚘𝚘𝚕 𝙵𝚘𝚗𝚝𝚜&lt;/td&gt;
&lt;td&gt;Math block&lt;/td&gt;
&lt;td&gt;Dev and retro profiles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Circled&lt;/td&gt;
&lt;td&gt;Ⓒⓞⓞⓛ Ⓕⓞⓝⓣⓢ&lt;/td&gt;
&lt;td&gt;Enclosed Alphanumerics&lt;/td&gt;
&lt;td&gt;Playful short names&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small caps&lt;/td&gt;
&lt;td&gt;ᴄᴏᴏʟ ꜰᴏɴᴛꜱ&lt;/td&gt;
&lt;td&gt;Phonetic blocks&lt;/td&gt;
&lt;td&gt;Aesthetic captions, subtle style&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fullwidth&lt;/td&gt;
&lt;td&gt;Ｃｏｏｌ　Ｆｏｎｔｓ&lt;/td&gt;
&lt;td&gt;Fullwidth Forms&lt;/td&gt;
&lt;td&gt;Vaporwave, spaced-out headers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few styles also restyle digits. Bold gives you 𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗, double struck gives you 𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡, and circled gives you ⓪①②③④⑤⑥⑦⑧⑨. Italic and script never got digit sets at all, so generators leave your numbers plain in those styles. One more reason to keep prices and dates in normal text.&lt;/p&gt;

&lt;h2&gt;
  
  
  How generators work under the hood
&lt;/h2&gt;

&lt;p&gt;Under the marketing, a generator is three small ideas stacked together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idea one: the mapping table.&lt;/strong&gt; For block styles, the table is almost embarrassingly simple. Each style starts at a known code point, and every letter is a fixed offset from there. Bold capital A is U+1D400, so bold capital B is U+1D401, and so on. A converter walks your text one character at a time, checks the exceptions list first, then does arithmetic. For assembled styles like circled and small caps, there is no arithmetic, so the table stores the full alphabet as a string and picks by position.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idea two: the exceptions.&lt;/strong&gt; This is where beginners get bitten, and I got bitten. If you do pure offset math for script text, the capital H lands on a reserved code point that renders as a box, because the real script H lives in Letterlike Symbols. The fix is a small hand-checked exceptions map: before doing any arithmetic, look the character up, and if it is one of the borrowed letters, use the borrowed code point. It is maybe twenty lines of data, and it is the difference between a generator that looks professional and one that quietly breaks on the name "Hannah."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idea three: counting honestly.&lt;/strong&gt; Almost every character in these blocks sits above U+FFFF, outside the Basic Multilingual Plane. In JavaScript and every UTF-16 system, each one is stored as a surrogate pair, two code units. I verified this against my own table while writing this guide: the four visible letters of 𝐂𝐨𝐨𝐥 report a &lt;code&gt;.length&lt;/code&gt; of 8, while spreading the string into code points gives the true count of 4. Any code that measures, slices, or validates styled text must count code points (&lt;code&gt;Array.from(text).length&lt;/code&gt;), or limits will lie and slices will cut characters in half.&lt;/p&gt;

&lt;p&gt;After conversion, one more invisible thing can happen to your text: &lt;strong&gt;normalization&lt;/strong&gt;. Some systems run input through a Unicode process called NFKC, which folds compatibility characters back to their plain forms. I tested this too: NFKC turns 𝐂𝐨𝐨𝐥, Ⓒⓞⓞⓛ, and Ｃｏｏｌ all back into "Cool". But it does nothing to ᴄᴏᴏʟ, because small caps characters are real phonetic letters with no plain equivalent to fold into. This is why the same styled name can survive in one app and come out plain in another.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden costs
&lt;/h2&gt;

&lt;p&gt;Cool fonts are not free. You pay in three currencies, and anyone using them deserves the full price list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost one: accessibility
&lt;/h3&gt;

&lt;p&gt;A screen reader cannot see your costume. It reads the actual characters, and for math block letters that usually means reading the full Unicode names, one letter at a time. A bold name can be announced as "mathematical bold capital A, mathematical bold small m..." all the way through the word, and some tools skip the characters entirely and read nothing at all. Neither outcome is your name.&lt;/p&gt;

&lt;p&gt;This is not just a vibe, it is a documented failure. The W3C's accessibility guidelines include a named failure, F71, for using lookalike characters without a text alternative, under Success Criterion 1.1.1 (Non-text Content). The official example is a word that looks like "cook" but is built from Greek and Cyrillic letters, which a screen reader announces as "Greek small letter lunate sigma, Cyrillic small letter o, Greek small letter omicron, k." Accessibility writers list social media text styled with mathematical alphanumeric symbols as the same failure pattern, and Braille displays have it worse, because Braille cells follow the actual characters too. One styled line in a bio is a decoration. A styled paragraph is a locked door.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost two: nobody can find you
&lt;/h3&gt;

&lt;p&gt;Search works on characters, so a styled word is a different string from the word people type. Your styled display name will not match a normal search for your name, a styled hashtag will not join the real hashtag's feed, and Ctrl+F on a page will sail straight past styled text without matching it. Whether a platform rescues you depends on whether it normalizes text behind the scenes, which is inconsistent and undocumented in most apps. My rule from experience: style for the people already looking at your profile, and keep at least one plain line, your real name, your niche, your city, for the people still trying to find you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost three: the security shadow
&lt;/h3&gt;

&lt;p&gt;The same lookalike trick has a dark mirror. Unicode publishes a file called confusables.txt, maintained under its security standard UTS #39, cataloguing characters that look alike across scripts, because attackers register domains and usernames with Cyrillic or Greek letters swapped in for Latin ones. These IDN homograph attacks have been known since a 2001 paper demonstrated the idea by registering a lookalike of a major software company's domain, and browsers still defend against them by sometimes showing a domain's ugly punycode form instead of the pretty one. Fancy math letters are a small, mostly harmless corner of this world, but the lesson transfers: text that looks identical is not necessarily the same text, and duplicate checks and moderation filters that compare raw strings can be fooled by styled spam. Use the costume for yourself, never to look like somebody else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where cool fonts work, and where they break
&lt;/h2&gt;

&lt;p&gt;Behavior varies by platform, because every platform made its own choices about validation, fonts, and normalization. The patterns I rely on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instagram and TikTok bios:&lt;/strong&gt; the natural habitat. Styled display names and bio lines render well on modern phones. Keep hashtags plain, because styled hashtags do not join the real feed; the searchable username underneath stays plain Latin anyway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;X (Twitter):&lt;/strong&gt; display names accept styled characters, but X's published weighting system charges more for characters outside the basic ranges, so styled text eats your limit faster than it looks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discord:&lt;/strong&gt; display names and server nicknames are the playground; the actual username is restricted to lowercase letters, numbers, underscores, and periods, so no style survives there. Server admins can also rename you, and people need to be able to type your name to @mention you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Games:&lt;/strong&gt; the strictest filter of all. Many games allowlist plain ASCII plus a few scripts for usernames and reject everything else, or show boxes. Bold and sans styles have the best survival rate; exotic styles and heavy zalgo get rejected or clamped first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn and professional profiles:&lt;/strong&gt; the characters usually render, but think twice. Recruiters search by plain text, applicant tracking systems parse by plain text, and a styled headline can quietly remove you from both. Keep the name and headline completely plain here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email and documents:&lt;/strong&gt; styled text in a subject line can trip spam filters (styled letters have shown up in spam for years, because lookalikes dodge keyword filters), and in documents it breaks find, sorting, and copy into other systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Underneath all of this sit two forces: font coverage (no glyph means an empty box, called tofu, and older budget Android phones are the classic victims, especially for circled letters) and the platform's own pipeline, which may normalize your text back to plain on save or clamp long combining mark runs, so your zalgo arrives tamer than you sent it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use cool fonts well
&lt;/h2&gt;

&lt;p&gt;Everything above compresses into one checklist, the same one I give everyone.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Style names and headings, never body text.&lt;/strong&gt; One or two styled lines per profile. The moment information matters, plain text takes over.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep a plain twin.&lt;/strong&gt; Your real name or keyword, typed normally, somewhere on the same profile. Decoration for the eye, plain text for the machines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never style anything that must match exactly.&lt;/strong&gt; Email addresses, discount codes, booking references, Wi-Fi passwords, and hashtags. Styled characters break matching silently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick boring styles for important places.&lt;/strong&gt; Sans bold and bold are the most widely supported and the most readable. Script and Fraktur are treats for short strings only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count double.&lt;/strong&gt; Budget two characters for every styled letter wherever a limit is tight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test like a stranger.&lt;/strong&gt; Paste the result on the oldest phone you can borrow, and once, just once, listen to your profile through a free screen reader. Ten minutes that will permanently change how much you style.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One accent per name.&lt;/strong&gt; A styled word or two inside a mostly plain name reads as personality. A fully styled everything reads as a puzzle.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Which style for which job
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wedding, beauty, celebration:&lt;/strong&gt; script for elegance, bold script when you need it to survive more phones. Keep it to names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gaming:&lt;/strong&gt; Fraktur or bold Fraktur for the dark look, double struck for a cleaner outline style. Check the game's filter with a throwaway name first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business and creators:&lt;/strong&gt; sans bold for headings. Intentional, professional, and it renders almost everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playful and cute:&lt;/strong&gt; circled letters, in short bursts. Weakest support on old devices, so never for your only name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subtle and aesthetic:&lt;/strong&gt; small caps. The quietest style here, and the one that survives normalization filters because the characters are real letters. Still invisible to search, so pair it with plain text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retro and vaporwave:&lt;/strong&gt; fullwidth and monospace. Maximum spacing, maximum nostalgia.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memes and horror:&lt;/strong&gt; zalgo, lightly. A little goes a long way, heavy zalgo is hostile to screen readers and gets clamped by platforms anyway.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Are cool fonts safe to use? Will I get banned?&lt;/strong&gt;&lt;br&gt;
The characters are ordinary Unicode text, not hacks or exploits, and mainstream platforms allow them in display names and bios. What can get you in trouble is impersonation: styling your name to look like someone else's fools people the same way a lookalike domain does. Be yourself, just fancier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does my styled text turn into boxes on some phones?&lt;/strong&gt;&lt;br&gt;
The phone has no glyph for those code points, so it draws an empty box called tofu. It is a font coverage problem on the viewing device, not something wrong with your text. Simpler styles, especially bold and sans bold, are covered by far more devices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why did my styled name become plain text after I saved it?&lt;/strong&gt;&lt;br&gt;
The platform probably normalized your input with NFKC or a similar process, which folds most styled characters back to plain letters. Small caps and upside down styles often survive, because their characters have no plain form to fold into. If a name matters, test it on the actual platform before announcing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do styled letters count as one character or two?&lt;/strong&gt;&lt;br&gt;
One character to a human, two code units to JavaScript and other UTF-16 systems, because each is stored as a surrogate pair. That is why limits drain at double speed, and why developers count code points instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use cool fonts in hashtags and usernames?&lt;/strong&gt;&lt;br&gt;
Hashtags, no: a styled hashtag is a different string and will not join the real conversation. Usernames, mostly no: platforms like Discord restrict usernames to plain lowercase letters, numbers, underscores, and periods. Display names are where the styling belongs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a way to make styled text accessible?&lt;/strong&gt;&lt;br&gt;
On the open web, yes: pair the styled text with a real text alternative, which is exactly what WCAG failure F71 asks for. In a bio field, you cannot attach alternatives, so the practical fix is the plain twin rule: the same information, in normal characters, right next to the styled version.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;By &lt;a href="https://medium.com/@iriskhan762" rel="noopener noreferrer"&gt;Iris Khan&lt;/a&gt;, beginner web dev, writing the build in public.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Guides
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/iris_khan_dev/the-hidden-price-of-fancy-text-what-those-curly-bio-letters-actually-cost-you-40bm"&gt;The Hidden Price of Fancy Text: What Those Curly Bio Letters Actually Cost You&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/iris_khan_dev/how-unicode-text-styling-actually-works-2026-471f"&gt;How Unicode Text Styling Actually Works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fontstylesguide.blogspot.com/2026/10/bold-text-generator-bold-fonts-for.html" rel="noopener noreferrer"&gt;Bold Text Generator: Bold Fonts for Captions, Bios and Posts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fontstylesguide.blogspot.com/2026/10/fancy-letters-to-z-copy-and-paste.html" rel="noopener noreferrer"&gt;Fancy Letters A to Z: Copy and Paste&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gitlab.com/iriskhan762/fancy-font-styles" rel="noopener noreferrer"&gt;Fancy Font Styles&lt;/a&gt;: the open chart and code resource behind the styles in this guide, with the full mapping table you can copy into your own projects.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>unicode</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Hidden Price of Fancy Text: What Those Curly Bio Letters Actually Cost You</title>
      <dc:creator>Iris Khan</dc:creator>
      <pubDate>Thu, 22 Oct 2026 12:16:38 +0000</pubDate>
      <link>https://dev.to/iris_khan_dev/the-hidden-price-of-fancy-text-what-those-curly-bio-letters-actually-cost-you-40bm</link>
      <guid>https://dev.to/iris_khan_dev/the-hidden-price-of-fancy-text-what-those-curly-bio-letters-actually-cost-you-40bm</guid>
      <description>&lt;p&gt;&lt;em&gt;I build text styling tools. I also spend an unreasonable amount of time reading the Unicode standard. Here is what styled text does to your character limits, your search visibility, and anyone using a screen reader, with the receipts.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Somewhere on your phone right now there is a bio written in letters like 𝒯𝒽𝒾𝓈. It looks like a font. Your phone treats it like one. And almost everything about how it behaves is different from what you think.&lt;/p&gt;

&lt;p&gt;I know because this is my whole thing. I am Iris, a beginner web developer, and I built a small Unicode text styler called Fonti that turns plain typing into 350+ styled versions you can copy into bios and captions. Building it forced me to learn what these characters actually are, and the honest answer surprised me: the styles are a clever hack on top of a system that was never designed for decoration, and the hack has real costs. This article is the full accounting, with proof for every claim, because the internet is full of font generator pages and almost none of them tell you any of this.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, the proof of what these characters are
&lt;/h2&gt;

&lt;p&gt;Open the official Unicode code charts (unicode.org/charts, the Mathematical Alphanumeric Symbols chart) and you will find the smoking gun. The letters in your bio are not a font file. They are individual characters living in a block called &lt;strong&gt;Mathematical Alphanumeric Symbols, U+1D400 to U+1D7FF&lt;/strong&gt;, a block the Unicode Consortium created so mathematicians could write variables in different styles inside equations.&lt;/p&gt;

&lt;p&gt;Three details from that chart prove how improvised the whole styling system is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Some letters are simply missing.&lt;/strong&gt; Look for the script small h. It is not there. The slot U+1D4BD exists, but the plain script capital H, C, and several others were never encoded in this block, because those shapes already existed elsewhere in Unicode as science symbols (ℋ is U+210B, SCRIPT CAPITAL H, from the Letterlike Symbols block). Generators quietly borrow those. Your "one font" is a patchwork stitched from at least two different Unicode blocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. One letter is a physics constant.&lt;/strong&gt; The italic small h every generator uses is ℎ, U+210E, the PLANCK CONSTANT. It was borrowed because the real mathematical italic h (at U+1D455) was never encoded at all. So when your name is written in italic script, the h in it is, formally, a unit of quantum physics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Nobody designed these to match.&lt;/strong&gt; Because the glyphs come from math notation, there was never a promise of consistent weight, spacing, or coverage. That is why some styles look slightly off, and why a few letters in a fancy style sometimes render in a visibly different design: the generator substituted the closest character it could find.&lt;/p&gt;

&lt;p&gt;This is all verifiable in about two minutes with a character inspector: paste a styled word into any Unicode analyzer and read the code points. I check mine against the chart constantly, because my users paste the output into profiles that matter to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost one: your characters count double
&lt;/h2&gt;

&lt;p&gt;Every character above U+FFFF, which includes every mathematical alphanumeric letter, is stored in UTF-16 as a &lt;strong&gt;surrogate pair&lt;/strong&gt;: two code units instead of one. Most platforms count characters in ways that feel this, even when they do not explain it.&lt;/p&gt;

&lt;p&gt;Here is the arithmetic for Instagram's 150-character bio limit. Take the name "Amelia" in bold script: 𝓐𝓶𝓮𝓵𝓲𝓪. That is 6 visible letters and 12 UTF-16 code units. Add a partner's name, a date, and two hearts, and a bio that looks short can hit the limit while looking half empty. I have watched users of styled text trim their bios three times without understanding why the counter moved so fast. Now you know: styled letters are heavy.&lt;/p&gt;

&lt;p&gt;X (Twitter) has its own published counting system (the twitter-text configuration, public on GitHub) where characters are weighted, and characters outside the basic ranges weigh more. The details differ by platform, but the direction is always the same: decoration costs space.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost two: search cannot see you
&lt;/h2&gt;

&lt;p&gt;Search engines match text by characters. The styled 𝒜 (U+1D49C-ish family) is not the letter A (U+0041), and to a search index they are different things unless the platform normalizes them.&lt;/p&gt;

&lt;p&gt;You can test this yourself. Put a unique styled word in a post, then search the platform for the same word typed normally. On most platforms, you will not find it. This is the trade nobody mentions: a styled display name is decoration for people who already found you, and invisibility for people searching your name. My standing advice, which I give to Fonti users too, is one styled line for looks and one plain line for being found. Keep your real name, your city, or your business keyword in plain text somewhere on the profile.&lt;/p&gt;

&lt;p&gt;There is a second, quieter version of this problem. Some systems apply Unicode normalization (a process called NFKC) that folds many styled characters back toward plain ones, and some do not. That is why the same styled name can be searchable in one app and invisible in another. There is no standard behavior, because decoration was never part of the design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost three: accessibility, and this is the serious one
&lt;/h2&gt;

&lt;p&gt;A screen reader does not see a styled word as a word. It reads character names or letter-by-letter approximations. The name 𝒥𝑜𝒽𝓃 can be announced as a string of "mathematical script capital J, mathematical bold script small o..." instead of "John". For a blind follower, your clever bio is a minute of robot spelling.&lt;/p&gt;

&lt;p&gt;This is not a theoretical concern. The Web Content Accessibility Guidelines (WCAG) exist because text is supposed to be text: selectable, searchable, readable by machines, and understandable by everyone. Styled Unicode fails most of that quietly. It still copies, it still looks fine to sighted readers, and the failure only shows up for the people least able to tell you about it.&lt;/p&gt;

&lt;p&gt;The second accessibility cost is visual. On devices whose fonts lack glyphs for the math block (older Android phones are the classic case), styled letters render as empty boxes, the phenomenon developers call &lt;strong&gt;tofu&lt;/strong&gt;. Your name becomes ▯▯▯▯. The older the phone, the more likely the boxes, which is why wedding and family accounts feel this cost first: the relatives you most want to read your bio are holding the phones least likely to render it.&lt;/p&gt;

&lt;h2&gt;
  
  
  So why does any of this work at all?
&lt;/h2&gt;

&lt;p&gt;Because Unicode is the one text system every platform already speaks. A font file needs installation and app support. A Unicode character just travels: copy it on one phone, paste it into another app, and it arrives intact, because it was never a picture of text. It is text, wearing a costume from the mathematics department.&lt;/p&gt;

&lt;p&gt;And honestly, the costume is delightful. A gaming name in blackletter, a couple's names in script, a small business bio with one elegant styled line: these genuinely make profiles feel personal in feeds that all look the same. I would not have built a styler if I did not believe that. The point of this article is not "never use fancy text." The point is to use it like someone who knows the price list:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One styled line, max two.&lt;/strong&gt; Style the display name or one bio line. Keep everything functional (dates, contact info, links-in-bio text) plain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always keep a plain-text twin.&lt;/strong&gt; Your searchable name or keyword, typed normally, somewhere on the profile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test on the oldest phone you can borrow.&lt;/strong&gt; If your name is boxes there, simplify the style. Bold script survives far more devices than delicate script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never style anything that must be copied exactly.&lt;/strong&gt; Booking references, email addresses, discount codes, Wi-Fi passwords. Styled characters will break matching systems silently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count double.&lt;/strong&gt; When a field has a tight limit, budget two characters for every styled letter and you will never be surprised mid-paste.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The receipts, if you want to check me
&lt;/h2&gt;

&lt;p&gt;Everything above can be verified without trusting me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Unicode code chart for Mathematical Alphanumeric Symbols (U+1D400 to U+1D7FF) on unicode.org shows the block, the gaps, and the borrowed letterlike symbols (ℋ U+210B, ℎ U+210E).&lt;/li&gt;
&lt;li&gt;Any online UTF-16 length checker will show a styled name counting roughly double its visible letters.&lt;/li&gt;
&lt;li&gt;X's text-counting rules are published in the twitter-text repository on GitHub.&lt;/li&gt;
&lt;li&gt;A free screen reader (NVDA on Windows, VoiceOver on iPhone) reading a styled bio aloud is the fastest proof of the accessibility cost. Try it once and you will never style a full paragraph again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I keep a running set of guides on the practical side of this: which styles survive which apps, tested one by one. If you want to play with the styles themselves and see the character counts as you type, that is exactly what I built &lt;a href="https://fonti.cool" rel="noopener noreferrer"&gt;Fonti&lt;/a&gt; for. It is free, it runs in the browser, and after this article you will use it with your eyes open, which is the only way I want anyone using it.&lt;/p&gt;

&lt;p&gt;Decoration is fine. Decoration you do not understand is a tax you pay in reach, in space, and in the people who quietly cannot read you. Now you know the tax code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try the styles:&lt;/strong&gt; every alphabet in this article is charted in the free &lt;a href="https://gitlab.com/iriskhan762/fancy-font-styles" rel="noopener noreferrer"&gt;Fancy Font Styles&lt;/a&gt; project on GitLab.&lt;/p&gt;

</description>
      <category>unicode</category>
      <category>webdev</category>
      <category>a11y</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How Unicode Text Styling Actually Works (2026)</title>
      <dc:creator>Iris Khan</dc:creator>
      <pubDate>Mon, 19 Oct 2026 10:46:18 +0000</pubDate>
      <link>https://dev.to/iris_khan_dev/how-unicode-text-styling-actually-works-2026-471f</link>
      <guid>https://dev.to/iris_khan_dev/how-unicode-text-styling-actually-works-2026-471f</guid>
      <description>&lt;p&gt;&lt;em&gt;Your bio "fonts" aren't fonts. They're just characters: weird, wonderful, off-the-map characters. Here's the technical story of how they work, why they sometimes turn into tofu boxes, and what every dev should know before using them.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I've been building Fonti (a little Unicode text styler) in public for a while now, and the number one question I get from developer friends is some version of: &lt;em&gt;"Wait, how is this not just a font?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Fair question. On the surface it looks like a font change. You paste 𝕙𝕖𝕝𝕝𝕠 into your bio and it renders "fancy." But no font was installed, no CSS was applied, nothing was styled at all. So what actually happened?&lt;/p&gt;

&lt;p&gt;This is the article I wish someone had handed me when I started: the real mechanism, the failure modes, and the honest limitations, from one beginner dev to other devs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trick: it's not styling, it's substitution
&lt;/h2&gt;

&lt;p&gt;Every character you see on screen is a &lt;strong&gt;code point&lt;/strong&gt;: a number assigned by the Unicode standard. The letter &lt;code&gt;h&lt;/code&gt; is U+0068. Normal stuff.&lt;/p&gt;

&lt;p&gt;But Unicode also contains entire &lt;em&gt;styled copies&lt;/em&gt; of the alphabet, originally designed for math notation. The big one is the &lt;strong&gt;Mathematical Alphanumeric Symbols&lt;/strong&gt; block (U+1D400 – U+1D7FF). When a generator turns &lt;code&gt;hello&lt;/code&gt; into &lt;code&gt;𝐡𝐞𝐥𝐥𝐨&lt;/code&gt;, it's doing plain substitution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;h&lt;/code&gt; (U+0068) → &lt;code&gt;𝐡&lt;/code&gt; (U+1D421, MATHEMATICAL BOLD SMALL H)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;e&lt;/code&gt; (U+0065) → &lt;code&gt;𝐞&lt;/code&gt; (U+1D41E)&lt;/li&gt;
&lt;li&gt;…and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. There's no formatting layer. The "style" is baked into which characters you picked. This is why it works everywhere text works, from Instagram bios and Discord usernames to tweets and plain &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; elements: those places render &lt;em&gt;characters&lt;/em&gt;, and these are valid characters.&lt;/p&gt;

&lt;p&gt;Try it: paste &lt;code&gt;𝐡𝐞𝐥𝐥𝐨&lt;/code&gt; into any input, any app, any OS. It renders styled (if the glyph exists) without a single line of CSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some copy-paste examples
&lt;/h2&gt;

&lt;p&gt;Here's what the generator approach produces, all plain Unicode:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bold:&lt;/strong&gt; 𝐡𝐞𝐥𝐥𝐨 𝐰𝐨𝐫𝐥𝐝&lt;br&gt;
&lt;strong&gt;Italic:&lt;/strong&gt; ℎ𝑒𝑙𝑙𝑜 𝑤𝑜𝑟𝑙𝑑&lt;br&gt;
&lt;strong&gt;Bold italic:&lt;/strong&gt; 𝒉𝒆𝒍𝒍𝒐 𝒘𝒐𝒓𝒍𝒅&lt;br&gt;
&lt;strong&gt;Script (cursive):&lt;/strong&gt; 𝒽ℯ𝓁𝓁ℴ 𝓌ℴ𝓇𝓁𝒹&lt;br&gt;
&lt;strong&gt;Fraktur (gothic):&lt;/strong&gt; 𝔥𝔢𝔩𝔩𝔬 𝔴𝔬𝔯𝔩𝔡&lt;br&gt;
&lt;strong&gt;Double-struck:&lt;/strong&gt; 𝕙𝕖𝕝𝕝𝕠 𝕨𝕠𝕣𝕝𝕕&lt;br&gt;
&lt;strong&gt;Monospace:&lt;/strong&gt; 𝚑𝚎𝚕𝚕𝚘 𝚠𝚘𝚛𝚕𝚍&lt;br&gt;
&lt;strong&gt;Sans-serif bold:&lt;/strong&gt; 𝗵𝗲𝗹𝗹𝗼 𝘄𝗼𝗿𝗹𝗱&lt;br&gt;
&lt;strong&gt;Circled:&lt;/strong&gt; ⓗⓔⓛⓛⓞ ⓦⓞⓡⓛⓓ&lt;br&gt;
&lt;strong&gt;Small caps (not really, but fun):&lt;/strong&gt; ʜᴇʟʟᴏ ᴡᴏʀʟᴅ&lt;/p&gt;

&lt;p&gt;Notice how the "script" row mixes blocks. That's normal. Generators typically pull from several Unicode ranges and fall back to the closest match for letters that don't have a styled twin.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why some glyphs turn into tofu boxes ▯
&lt;/h2&gt;

&lt;p&gt;You've seen it: you paste a style somewhere and get &lt;code&gt;▯▯▯▯▯&lt;/code&gt; or blank squares. That box is called &lt;strong&gt;tofu&lt;/strong&gt;, and the reason is simple: &lt;strong&gt;the user's font doesn't have a glyph for that code point.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unicode assigns the number; fonts decide how (or whether) to draw it. The Mathematical Alphanumeric Symbols live at U+1D400+, which is in the &lt;em&gt;supplementary planes&lt;/em&gt;, outside the Basic Multilingual Plane (U+0000–U+FFFF) that most legacy fonts cover. Common system fonts on iOS/Android/modern desktop cover them fine, but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Older Windows fonts often don't include U+1D400–U+1D7FF.&lt;/li&gt;
&lt;li&gt;Some platforms apply their own font stack to usernames/bios and won't do proper fallback.&lt;/li&gt;
&lt;li&gt;A few apps actively strip non-BMP characters (see below).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So "works on my phone" ≠ "works everywhere." It's a font coverage problem, not a Unicode problem.&lt;/p&gt;
&lt;h2&gt;
  
  
  The part devs always ask about: surrogate pairs and string length
&lt;/h2&gt;

&lt;p&gt;Here's where it bites in code. Characters above U+FFFF can't fit in one UTF-16 code unit, so UTF-16 (JavaScript strings, Java, .NET) encodes them as a &lt;strong&gt;surrogate pair&lt;/strong&gt;: two code units per character.&lt;/p&gt;

&lt;p&gt;Watch this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;𝐡&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;        &lt;span class="c1"&gt;// 2, not 1!&lt;/span&gt;
&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;𝐡&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;   &lt;span class="c1"&gt;// 1: the spread operator iterates code points&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;𝐡𝐞𝐥𝐥𝐨&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;   &lt;span class="c1"&gt;// 10, not 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five visible "letters," ten UTF-16 code units. This is why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Character limits break.&lt;/strong&gt; A platform counting with &lt;code&gt;.length&lt;/code&gt; sees "hello" styled as 10 characters. Twitter/X bio limits, Discord's username length checks, database &lt;code&gt;VARCHAR(30)&lt;/code&gt; columns. They all count code units, not glyphs. A 75-char display name can blow past a 32-char limit while looking half the size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slicing breaks.&lt;/strong&gt; &lt;code&gt;"𝐡𝐞𝐥𝐥𝐨".slice(0, 3)&lt;/code&gt; gives you &lt;code&gt;𝐡&lt;/code&gt; + half of &lt;code&gt;𝐞&lt;/code&gt;: a dangling surrogate, which renders as &lt;code&gt;�&lt;/code&gt; (U+FFFD, the replacement character).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emojis have the same problem&lt;/strong&gt;, for the same reason. If your validation already handles emoji correctly (code-point-aware), styled text mostly behaves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rule of thumb: whenever you measure, slice, or validate user text, count &lt;strong&gt;code points&lt;/strong&gt; (&lt;code&gt;[...str].length&lt;/code&gt;, Python's &lt;code&gt;len()&lt;/code&gt; on a str, etc.), not code units.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why some platforms strip or reject styled text
&lt;/h2&gt;

&lt;p&gt;A few reasons you'll hit in the wild:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Normalization.&lt;/strong&gt; Some backends run input through NFC normalization. Most math-alphanumeric chars survive it, but combining-mark-based styles (z͟a͟l͟g͟o͟ text, diacritic stacks) get mangled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Character filters.&lt;/strong&gt; Games and chat apps often allowlist printable ASCII + a few scripts. Anything else gets replaced or rejected. This is why a style works on TikTok but fails in a game's username field.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rendering pipelines.&lt;/strong&gt; Discord, for instance, renders usernames in specific fonts in some UI spots; coverage varies between desktop, mobile, and web.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spam heuristics.&lt;/strong&gt; Heavy zalgo/combining text can trip abuse filters. Another reason to keep it clean.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're building anything that accepts display names, the pragmatic approach is: allow the full range, count code points, and set generous limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations (the part most generators won't tell you)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Screen readers.&lt;/strong&gt; A screen reader doesn't read &lt;code&gt;𝐡𝐞𝐥𝐥𝐨&lt;/code&gt; as "hello." Many announce it as "mathematical bold small h, mathematical bold small e…", letter by letter, with the style prefix. An entire styled bio can be an awful experience for blind users. Use styled text sparingly, never for essential info.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search and Ctrl+F.&lt;/strong&gt; Searching a page for "hello" will not match &lt;code&gt;𝐡𝐞𝐥𝐥𝐨&lt;/code&gt;. Different code points, no match. Same reason Google treats them as distinct strings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's not a font.&lt;/strong&gt; You can't change weight, size, or color beyond the fixed variants Unicode provides. There's no "bold script Fraktur with serifs." The set is closed, and new styled alphabets basically never get added (Unicode's policy is now &lt;em&gt;not&lt;/em&gt; to encode more styled Latin letters).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy-paste fidelity.&lt;/strong&gt; Pasting between apps with different font coverage can silently turn characters into tofu or question marks on the receiving end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readability.&lt;/strong&gt; Fraktur and double-struck look cool and are genuinely hard to read. Long paragraphs in styled text are a readability tax on everyone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this means "don't use it." It means use it like seasoning, not the whole meal.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Are styled Unicode characters the same as fonts?&lt;/strong&gt;&lt;br&gt;
No. Fonts change how a character &lt;em&gt;looks&lt;/em&gt;. Styled Unicode swaps in &lt;em&gt;different characters&lt;/em&gt; that happen to look styled. The style travels with the text because it &lt;em&gt;is&lt;/em&gt; the text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does my styled name show as boxes on some devices?&lt;/strong&gt;&lt;br&gt;
The device's fonts lack glyphs for those code points (usually U+1D400+). It's a font-coverage issue on the rendering side. The characters themselves are fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do styled characters count as one character or two?&lt;/strong&gt;&lt;br&gt;
One code point, but two UTF-16 code units (a surrogate pair). &lt;code&gt;"𝐡".length === 2&lt;/code&gt; in JavaScript. Count code points, not code units, when validating input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will styled text work in every app?&lt;/strong&gt;&lt;br&gt;
Mostly, but not always. Some games and platforms filter non-standard characters, and font coverage varies. Test the specific style on the specific platform before committing to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is using styled Unicode in a bio "hacking"?&lt;/strong&gt;&lt;br&gt;
No, it's just Unicode. Platforms render what you paste. Some platforms filter it, which is their right, but there's nothing exploit-like about pasting valid characters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I make my own styled alphabet?&lt;/strong&gt;&lt;br&gt;
Not really. You can't add code points to Unicode. What generators do is map existing styled characters onto ASCII input. You &lt;em&gt;can&lt;/em&gt; build your own mapping tables (that's literally how these tools work), combining different blocks creatively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does styled text hurt SEO or accessibility?&lt;/strong&gt;&lt;br&gt;
For SEO: search engines see distinct code points, so styled keywords don't match plain-text queries. For accessibility: screen readers often spell out the style names letter by letter, which is rough. Keep styled text decorative and short.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do some letters look "missing" in certain styles?&lt;/strong&gt;&lt;br&gt;
Unicode never encoded styled versions of every letter in every style. Generators substitute the closest available character or leave the plain letter. For example, there's no dedicated "script capital" for every letter, so mappings get creative.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Guides
&lt;/h2&gt;

&lt;p&gt;If you want to play with the actual styles after understanding the machinery, a couple of practical roundups I put together: &lt;a href="https://telegra.ph/Best-Fonts-for-Instagram-Bio-2026-30-Copy-Paste-Styles-That-Actually-Work-10-17" rel="noopener noreferrer"&gt;Best Fonts for Instagram Bio 2026&lt;/a&gt; (30 copy-paste styles, tested), &lt;a href="https://telegra.ph/Aesthetic-Text-Styles-2026-Copy-Paste-Fonts-for-Bios-Captions--Usernames-10-17" rel="noopener noreferrer"&gt;Aesthetic Text Styles 2026&lt;/a&gt; (vibe-organized, from coquette to dark academia), and for the gamers, &lt;a href="https://telegra.ph/Gaming-Name-Fonts-2026-PUBG-Free-Fire-Valorant--More-10-18-2" rel="noopener noreferrer"&gt;Gaming Name Fonts&lt;/a&gt; (what actually survives game username filters). Also worth a look: &lt;a href="https://telegra.ph/TikTok-Bio-Fonts-2026-Copy-Paste-Styles-That-Fit-TikToks-Small-Text-10-20" rel="noopener noreferrer"&gt;TikTok bio fonts guide&lt;/a&gt;, &lt;a href="https://rentry.co/discord-name-fonts-2026" rel="noopener noreferrer"&gt;Discord name fonts guide&lt;/a&gt;, &lt;a href="https://write.as/xoz5jankycvm0.md" rel="noopener noreferrer"&gt;WhatsApp text formatting guide&lt;/a&gt;, and &lt;a href="https://fontstylesguide.blogspot.com/2026/10/fancy-text-generators-2026-how-they.html" rel="noopener noreferrer"&gt;Fancy text generators, under the hood&lt;/a&gt;. &lt;a href="https://telegra.ph/Gaming-Name-Fonts-2026-Fancy-Styles-for-PUBG-Free-Fire-Roblox-and-More-10-20" rel="noopener noreferrer"&gt;Gaming name fonts guide&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;By &lt;a href="https://medium.com/@iriskhan762" rel="noopener noreferrer"&gt;Iris Khan&lt;/a&gt;, beginner web dev, writing the build in public.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Go deeper:&lt;/strong&gt; &lt;a href="https://dev.to/iris_khan_dev/the-hidden-price-of-fancy-text-what-those-curly-bio-letters-actually-cost-you-40bm"&gt;The Hidden Price of Fancy Text: What Those Curly Bio Letters Actually Cost You&lt;/a&gt; covers what styled text does to your character limits, search visibility, and screen readers, with the receipts from the Unicode standard itself.&lt;/p&gt;

&lt;p&gt;The full mapping table behind these styles lives in a public GitLab snippet, the &lt;a href="https://gitlab.com/-/snippets/6071289" rel="noopener noreferrer"&gt;Unicode Font Styles Mapping Table&lt;/a&gt;, and every alphabet is also published as a copy-paste chart in the &lt;a href="https://gitlab.com/iriskhan762/fancy-font-styles" rel="noopener noreferrer"&gt;Fancy Font Styles&lt;/a&gt; project.&lt;/p&gt;

</description>
      <category>unicode</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Went Quiet for a Month. Here Is What I Was Overthinking.</title>
      <dc:creator>Iris Khan</dc:creator>
      <pubDate>Mon, 12 Oct 2026 19:15:21 +0000</pubDate>
      <link>https://dev.to/iris_khan_dev/i-went-quiet-for-a-month-here-is-what-i-was-overthinking-1dln</link>
      <guid>https://dev.to/iris_khan_dev/i-went-quiet-for-a-month-here-is-what-i-was-overthinking-1dln</guid>
      <description>&lt;p&gt;This is part 5 of my beginner journey, and I owe you an apology first.&lt;/p&gt;

&lt;p&gt;I went quiet for about a month. This is my first real project, and I did not know how much time it would take. For people who build things every day, a lot of this will look basic. For me it was a big deal, and I did not want to post updates until I had something honest to say.&lt;/p&gt;

&lt;p&gt;If you are new here, the earlier parts are &lt;a href="https://dev.to/iris_khan_dev/i-have-no-idea-what-to-learn-first-and-heres-how-im-deciding-4f4a"&gt;part 1&lt;/a&gt;, &lt;a href="https://dev.to/iris_khan_dev/from-overthinking-to-actually-building-something-3ndc"&gt;part 2&lt;/a&gt;, &lt;a href="https://dev.to/iris_khan_dev/i-decided-to-build-it-then-i-had-to-actually-decide-how-86i"&gt;part 3&lt;/a&gt; and &lt;a href="https://dev.to/iris_khan_dev/copy-paste-fonts-are-not-fonts-here-is-what-broke-my-code-4kc9"&gt;part 4&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The name took the longest
&lt;/h2&gt;

&lt;p&gt;I started with the name Fontly and deployed the site under it. Then I did my research. I found that domains in the same niche already existed, and so did apps with similar names. I started worrying about brand and copyright problems. Then I learned that people can actually take legal action over a name, and that made the worry bigger.&lt;/p&gt;

&lt;p&gt;So I kept searching. I wanted a name that was short, easy to say, and related to the site. I did not want a long name. Every option I liked had some problem.&lt;/p&gt;

&lt;p&gt;And there was a quiet pressure on top of it. Why was I taking so long to decide one thing? That feeling made me overthink even more.&lt;/p&gt;

&lt;p&gt;In the end I said: enough. Close your eyes and pick Fonti.&lt;/p&gt;

&lt;h2&gt;
  
  
  Renaming after I had already deployed
&lt;/h2&gt;

&lt;p&gt;Here is the funny part. The site was already deployed under the old name, so I had to go back and change everything to Fonti.&lt;/p&gt;

&lt;p&gt;And a confession. In part 4 I already talked about the new name, but at that time I had not even bought the domain. When it was finally time to buy it, the overthinking came back and it took more time again. This time I stopped thinking and took action.&lt;/p&gt;

&lt;p&gt;It might sound weird, or even funny. But I think this is what happens when you are new to something. A small decision feels huge when you are afraid of getting it wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then my PC died
&lt;/h2&gt;

&lt;p&gt;In the middle of all this, the motherboard and the processor on my PC both died. I replaced them and started again.&lt;/p&gt;

&lt;p&gt;It was frustrating. But it reminded me of part 1 and part 2. When I get stuck, my first instinct is to think more. Every time I took action instead, things moved again.&lt;/p&gt;

&lt;h2&gt;
  
  
  I chased speed, then stopped
&lt;/h2&gt;

&lt;p&gt;Speed was the top priority for my site. I started chasing it hard, and I reached a level that most beginners do not even think about.&lt;/p&gt;

&lt;p&gt;Then I stopped. Overdoing anything is unhealthy, and speed is no exception. After a certain point it becomes overkill: every small gain costs more time than the one before it, and that time was being taken from things that still needed finishing. A faster site is nice. A finished site is better. So I decided that "fast and good enough" is the goal, not "perfect".&lt;/p&gt;

&lt;h2&gt;
  
  
  One technical thing from this month
&lt;/h2&gt;

&lt;p&gt;I also overthought whether my code was correct. After part 4 I had an exception list for the Unicode holes, and I did not want to trust my memory. So I wrote a script that runs every style on A to Z, a to z and 0 to 9, and checks two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never U+FFFD:&lt;/strong&gt; the replacement character means something went wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never unchanged:&lt;/strong&gt; a letter should not come back as itself, unless I declared on purpose that it should.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the idea, simplified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;generated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;generated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;codePointAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mh"&gt;0xfffd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" produced U+FFFD`&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generated&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;plain&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isDeclared&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" passed through unchanged`&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RESULT: PASS. 17 styles, all uppercase/lowercase/digit characters verified. 8 decorations, every prefix and suffix character verified.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I felt safe. But a passing test only tells me it did not find a problem. It does not tell me it was &lt;em&gt;able&lt;/em&gt; to find one.&lt;/p&gt;

&lt;p&gt;So I tested the test. In my script style, each exception lives in a &lt;code&gt;substitutions&lt;/code&gt; list. I deleted one line, &lt;code&gt;B: 0x212c&lt;/code&gt;, which brings back the exact bug from part 4. Then I ran the script again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RESULT: PASS. 17 styles, all uppercase/lowercase/digit characters verified. 8 decorations, every prefix and suffix character verified.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It still passed. And the line for B in the output said:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;B -&amp;gt; 𝒝  (U+1D49D)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a code point in one of the holes. The bug was back, and my test was happy.&lt;/p&gt;

&lt;p&gt;The reason is simple. &lt;code&gt;U+1D49D&lt;/code&gt; is not &lt;code&gt;U+FFFD&lt;/code&gt;, and it is not the same as the plain &lt;code&gt;B&lt;/code&gt;. JavaScript accepted the number without complaint, because &lt;code&gt;String.fromCodePoint(0x1D49D)&lt;/code&gt; gives a perfectly valid string. The empty box only appears later, when a screen tries to draw it. As far as I can tell, &lt;code&gt;U+FFFD&lt;/code&gt; shows up when text is decoded from broken data, not when you build a string from a number. So my first check could never catch this.&lt;/p&gt;

&lt;p&gt;The fix was to ask Unicode directly. JavaScript regular expressions have a property for it: &lt;code&gt;\p{Cn}&lt;/code&gt; means "a code point that has not been assigned".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UNASSIGNED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\p&lt;/span&gt;&lt;span class="sr"&gt;{Cn}/u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UNASSIGNED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generated&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" produced &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;codePointHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generated&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;, a code point Unicode has not assigned`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the line still deleted, the script now says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RESULT: FAIL. 1 failure(s):
  [script] uppercase "B" produced U+1D49D, a code point Unicode has not assigned
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I put the line back and ran it once more. PASS, and this time it means something.&lt;/p&gt;

&lt;p&gt;Two honest limits. First, &lt;code&gt;\p{Cn}&lt;/code&gt; depends on the Unicode version inside whatever runs the code. On my computer, Node reports Unicode 17.0 (&lt;code&gt;process.versions.unicode&lt;/code&gt;), and an older runtime might think a newer character is unassigned. So I only run this check on my own machine, never in the browser. Second, assigned does not mean visible: a valid character can still show as a box on a device whose fonts do not have it. So PASS means no holes. It does not mean it works everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overthinking costs more than a small mistake:&lt;/strong&gt; a name does not need to be perfect. It needs to be good enough to start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Things break in boring ways:&lt;/strong&gt; a dead PC and a rename took as much time as any clever bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Break your code on purpose, once:&lt;/strong&gt; if the test does not fail, it was not protecting you.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is next
&lt;/h2&gt;

&lt;p&gt;I have been building more than I have been posting. In the next part I will show you the site live, and tell you the full story of how it came together.&lt;/p&gt;

&lt;p&gt;One question for you: what is one decision you overthought on your first project, and how did you finally make it?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>buildinpublic</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Copy-Paste Fonts Are Not Fonts. Here Is What Broke My Code</title>
      <dc:creator>Iris Khan</dc:creator>
      <pubDate>Wed, 02 Sep 2026 17:27:28 +0000</pubDate>
      <link>https://dev.to/iris_khan_dev/copy-paste-fonts-are-not-fonts-here-is-what-broke-my-code-4kc9</link>
      <guid>https://dev.to/iris_khan_dev/copy-paste-fonts-are-not-fonts-here-is-what-broke-my-code-4kc9</guid>
      <description>&lt;p&gt;This is part 4 of my beginner journey.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/iris_khan_dev/i-have-no-idea-what-to-learn-first-and-heres-how-im-deciding-4f4a"&gt;part 1&lt;/a&gt; I did not know what to learn first. In &lt;a href="https://dev.to/iris_khan_dev/from-overthinking-to-actually-building-something-3ndc"&gt;part 2&lt;/a&gt; I stopped overthinking and picked a project: &lt;a href="https://fonti.cool/" rel="noopener noreferrer"&gt;Fonti, a free copy-paste font generator&lt;/a&gt;. In &lt;a href="https://dev.to/iris_khan_dev/i-decided-to-build-it-then-i-had-to-actually-decide-how-86i"&gt;part 3&lt;/a&gt; I chose my tools (Astro, Git, Cloudflare Pages) and promised the next post would be about the actual build.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: I renamed the project from Fontly to Fonti since part 3. Same tool, better name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is that post. My first real code of this series.&lt;/p&gt;

&lt;p&gt;And it starts with me being completely wrong about how these tools work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I thought I was building
&lt;/h2&gt;

&lt;p&gt;My idea was simple. The user types text. I apply a font. They copy it and paste it into their Instagram bio.&lt;/p&gt;

&lt;p&gt;I spent a few days confused about one part of that plan. How does a font survive a copy-paste? If I load a font on my website, that font lives on my website. Instagram does not have it. So how does the style travel?&lt;/p&gt;

&lt;p&gt;The answer is that it does not. There is no font. There never was.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real trick
&lt;/h2&gt;

&lt;p&gt;When you paste 𝐛𝐨𝐥𝐝 text somewhere, you are not sending style information. You are sending different characters that are shaped like bold letters.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;b&lt;/code&gt; in that bold word is not the letter &lt;code&gt;b&lt;/code&gt;. It is a math symbol that looks like a bold &lt;code&gt;b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Unicode has a block called &lt;strong&gt;Mathematical Alphanumeric Symbols&lt;/strong&gt;. It lives at &lt;code&gt;U+1D400&lt;/code&gt; to &lt;code&gt;U+1D7FF&lt;/code&gt;. It has 1024 slots, 996 of them are used, and 28 are empty on purpose. It was added in Unicode 3.1 in the year 2001.&lt;/p&gt;

&lt;p&gt;Why does it exist? Because mathematicians need 𝐴 and 𝔸 and 𝒜 to mean three different things in the same paper. In math, the style is part of the meaning. So Unicode gave each style its own real character.&lt;/p&gt;

&lt;p&gt;Social media users then found these characters and started using them for stylish bios. That is the whole industry of "font generators".&lt;/p&gt;

&lt;p&gt;So my tool is not a font tool. It is a lookup table. That is it.&lt;/p&gt;

&lt;p&gt;This also connects back to something from part 3. I chose Astro because it sends very little JavaScript by default, and only sends it where the page really needs it. Now I know exactly what that JavaScript is: one small function that swaps characters. Everything else on the site can stay as plain HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  My first version
&lt;/h2&gt;

&lt;p&gt;If the styled letters are stored in alphabetical order, then converting text is just simple math.&lt;/p&gt;

&lt;p&gt;Normal &lt;code&gt;A&lt;/code&gt; is at &lt;code&gt;U+0041&lt;/code&gt;. Bold &lt;code&gt;A&lt;/code&gt; is at &lt;code&gt;U+1D400&lt;/code&gt;. So I take the letter's number, subtract the start of the normal alphabet, and add the start of the bold alphabet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OFFSETS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x1D400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x1D41A&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x1D434&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x1D44E&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x1D49C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x1D4B6&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;fraktur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x1D504&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x1D51E&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;doubleStruck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x1D538&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x1D552&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;sansSerif&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x1D5A0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x1D5BA&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;convert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;OFFSETS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCodePoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;upper&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCodePoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;97&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was happy. It felt clean. No font files, no library, no dependency. Just math.&lt;/p&gt;

&lt;p&gt;Bold worked. Italic mostly worked. Sans-serif worked.&lt;/p&gt;

&lt;p&gt;Then I tested script style and got this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;𝒜&lt;/code&gt; for A. Then an empty box for B.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it broke
&lt;/h2&gt;

&lt;p&gt;The block has holes in it. And the holes are there on purpose.&lt;/p&gt;

&lt;p&gt;Before this big math block existed, Unicode had already added a few of these styled letters in a &lt;strong&gt;different&lt;/strong&gt; block called &lt;strong&gt;Letterlike Symbols&lt;/strong&gt; at &lt;code&gt;U+2100&lt;/code&gt; to &lt;code&gt;U+214F&lt;/code&gt;. They were added because people already used them as single math symbols. Things like ℝ for real numbers and ℋ for a Hilbert space.&lt;/p&gt;

&lt;p&gt;So when the big block was designed later, those letters were left out. Unicode did not want to encode the same character twice. The official Unicode chart says this directly about the double-struck letters: they are "omitted here to avoid duplicate encoding."&lt;/p&gt;

&lt;p&gt;The result for me is simple. My math formula was pointing at empty slots. Empty slot means empty box on screen.&lt;/p&gt;

&lt;p&gt;I had to build an exception list. Here is every hole that affects normal A to Z text, and where the real character actually lives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EXCEPTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u210E&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                    &lt;span class="c1"&gt;// ℎ  (Planck constant)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;B&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u212C&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;E&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u2130&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;F&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u2131&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;H&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u210B&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;I&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u2110&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;L&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u2112&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;M&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u2133&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;R&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u211B&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u212F&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u210A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u2134&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;fraktur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u212D&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;H&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u210C&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;I&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u2111&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;R&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u211C&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u2128&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;doubleStruck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u2102&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;H&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u210D&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;N&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u2115&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;P&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u2119&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u211A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;R&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u211D&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u2124&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is 24 exceptions in four styles.&lt;/p&gt;

&lt;p&gt;Now look at which styles are &lt;strong&gt;not&lt;/strong&gt; in that list. Bold, bold italic, bold script, bold fraktur, sans-serif and sans-serif bold are all fine. Their letters sit in one clean run with no gaps. Pure math works perfectly for them.&lt;/p&gt;

&lt;p&gt;Only the four styles that overlap with old math notation are broken. That makes sense once you know the history, and it is impossible to guess before you know it.&lt;/p&gt;

&lt;p&gt;My favourite one is italic. The whole italic alphabet is fine except lowercase &lt;code&gt;h&lt;/code&gt;. Just one letter. Why? Because ℎ at &lt;code&gt;U+210E&lt;/code&gt; was already there as the Planck constant symbol from physics.&lt;/p&gt;

&lt;p&gt;Here is the fixed function. The change is small. Check the exception list first, then fall back to math.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;convert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;OFFSETS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gaps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;EXCEPTIONS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCodePoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;upper&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCodePoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;97&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  One thing I still cannot explain
&lt;/h2&gt;

&lt;p&gt;Script lowercase &lt;code&gt;e&lt;/code&gt;, &lt;code&gt;g&lt;/code&gt; and &lt;code&gt;o&lt;/code&gt; were all skipped, because ℯ, ℊ and ℴ already existed in the older block.&lt;/p&gt;

&lt;p&gt;But script lowercase &lt;code&gt;l&lt;/code&gt; &lt;strong&gt;was&lt;/strong&gt; added to the new block at &lt;code&gt;U+1D4C1&lt;/code&gt;, even though ℓ already existed at &lt;code&gt;U+2113&lt;/code&gt;. Exactly the same situation, opposite decision.&lt;/p&gt;

&lt;p&gt;If someone knows the reason behind that, please tell me in the comments. I looked and I could not find it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second thing that bit me
&lt;/h2&gt;

&lt;p&gt;These characters live above &lt;code&gt;U+FFFF&lt;/code&gt;. In JavaScript that means one visible character takes two slots inside the string.&lt;/p&gt;

&lt;p&gt;This is why my code uses &lt;code&gt;[...text]&lt;/code&gt; and not &lt;code&gt;text.split("")&lt;/code&gt;. If you use &lt;code&gt;split("")&lt;/code&gt;, it cuts these characters in half and you get broken output.&lt;/p&gt;

&lt;p&gt;Before you write any code here, open your browser console and run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;𝐀&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whatever number you see there will tell you why string length cannot be trusted in this project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest part
&lt;/h2&gt;

&lt;p&gt;I have to say this, because I found it while researching and it changed how I think about my own project.&lt;/p&gt;

&lt;p&gt;Unicode tells people not to do what I am doing. The Unicode Standard, chapter 22, says: "Characters from the Mathematical Alphanumeric symbols block should not be used to represent styling of nonmathematical text."&lt;/p&gt;

&lt;p&gt;These characters were made to be different from normal letters on purpose. Using them as decoration is a misuse of the system.&lt;/p&gt;

&lt;p&gt;And there is a real cost. Spammers use this exact trick to get past keyword filters, because a filter looking for a word will not match the same word written in math symbols. People have documented it in spam emails and even inside URLs.&lt;/p&gt;

&lt;p&gt;So Fonti will ship with a warning next to the output. It will say clearly that this text is not real formatting, that it can break search and filters, and that it is a bad choice anywhere the text needs to be read by a machine.&lt;/p&gt;

&lt;p&gt;One more thing I do not know yet: how screen readers announce these characters. I am not going to guess. I will test it properly and write about what I actually find.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking back at part 1
&lt;/h2&gt;

&lt;p&gt;In part 1, I was stuck choosing between HTML, CSS, JavaScript, Python and React. I felt like I had to pick the right one before I could start.&lt;/p&gt;

&lt;p&gt;This whole feature is plain JavaScript. A map, a loop, and some numbers. No framework, no library, no React.&lt;/p&gt;

&lt;p&gt;The hard part was never the language. It was understanding the problem. I spent one hour writing the code and three days figuring out that fonts were not involved at all.&lt;/p&gt;

&lt;p&gt;That is the thing I wish someone had told me in part 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two questions
&lt;/h2&gt;

&lt;p&gt;First: the script lowercase &lt;code&gt;l&lt;/code&gt; inconsistency above. Does anyone know the story?&lt;/p&gt;

&lt;p&gt;Second: are there other Unicode ranges with reserved holes like this, where offset math quietly produces invalid characters? Letterlike Symbols is the only overlap I found, but I doubt it is the only one that exists.&lt;/p&gt;

&lt;p&gt;Next post: building the actual UI and the copy button, which turned out to have its own surprise.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://unicode.org/charts/PDF/U1D400.pdf" rel="noopener noreferrer"&gt;Mathematical Alphanumeric Symbols code chart (PDF)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.unicode.org/charts/nameslist/n_1D400.html" rel="noopener noreferrer"&gt;Mathematical Alphanumeric Symbols names list&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-22/" rel="noopener noreferrer"&gt;Unicode Standard, Chapter 22&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.alanwood.net/unicode/letterlike_symbols.html" rel="noopener noreferrer"&gt;Letterlike Symbols character table&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>unicode</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Decided to Build It. Then I Had to Actually Decide How.</title>
      <dc:creator>Iris Khan</dc:creator>
      <pubDate>Sun, 30 Aug 2026 12:29:30 +0000</pubDate>
      <link>https://dev.to/iris_khan_dev/i-decided-to-build-it-then-i-had-to-actually-decide-how-86i</link>
      <guid>https://dev.to/iris_khan_dev/i-decided-to-build-it-then-i-had-to-actually-decide-how-86i</guid>
      <description>&lt;p&gt;Last time, I &lt;a href="https://dev.to/iris_khan_dev/from-overthinking-to-actually-building-something-3ndc"&gt;decided to stop overthinking and start building&lt;/a&gt; Fontly, a free copy-paste font generator. Before writing a single line of the actual tool, I had to make a decision I thought would be quick and ended up being the thing I spent the most time on: the tech stack.&lt;/p&gt;

&lt;p&gt;Here's how that thinking actually went.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting point: "just make it a static site"
&lt;/h2&gt;

&lt;p&gt;My first instinct was the simplest possible option: a plain static site. No framework, no build step, just HTML/CSS/JS. For a small tool like this, that felt like the obvious beginner-friendly choice, and honestly, most "how to build your first project" advice points exactly there.&lt;/p&gt;

&lt;p&gt;For a single page with one tool on it, that instinct isn't wrong. But the more I thought about where this project needed to go, the more that plan started to strain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where plain static started to break down
&lt;/h2&gt;

&lt;p&gt;A few things I kept running into when I actually mapped out what Fontly needed, beyond just "the tool works":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This isn't going to stay one page:&lt;/strong&gt; To be findable at all, a font generator needs supporting content: explainer pages, FAQs, maybe category-specific pages down the line, and hand-writing and hand-linking a growing pile of HTML files gets messy fast, with no structure keeping it consistent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO isn't just meta tags:&lt;/strong&gt; Clean semantic markup, sitemaps, fast load times, and consistent page structure all matter, and doing all of that by hand, correctly, on every new page is exactly the kind of repetitive work that's easy to get subtly wrong as a beginner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content and code kept wanting to be separate:&lt;/strong&gt; The actual words on the page (headings, FAQ answers, descriptions) are things I want to be able to edit and expand without touching layout code every time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point was the real turning point. I didn't need "a framework" for the sake of having one, I needed a way to manage &lt;em&gt;content&lt;/em&gt; that wasn't tangled up with markup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Astro specifically
&lt;/h2&gt;

&lt;p&gt;Once I framed it as a content-management problem rather than an app-building problem, Astro made sense for a few specific reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Content collections:&lt;/strong&gt; Astro lets me define structured content (pages, FAQ entries, future blog posts) separately from how they're rendered, so adding a new page later means adding content, not rewriting HTML.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ships minimal JavaScript by default:&lt;/strong&gt; Unlike a full app framework, Astro renders to static HTML and only sends JavaScript for the parts that actually need interactivity, like the font-conversion tool itself. That matters for both load speed and SEO.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO fundamentals are built in&lt;/strong&gt;, not bolted on: sitemap generation, clean output, and a structure that doesn't fight against good practices by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's still simple underneath:&lt;/strong&gt; The interactive logic is still plain TypeScript/JavaScript, Astro isn't replacing that, it's just handling the structure and content around it in a way that scales better than hand-written HTML would have.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The honest tradeoff
&lt;/h2&gt;

&lt;p&gt;This wasn't a free upgrade. Astro means a build step, a bit of a learning curve, and more moving pieces than plain HTML/CSS/JS, genuinely more than I needed for day one. I went with it anyway because I was planning for where the project needed to go (more content, better SEO structure, room to grow) rather than just what got something on screen fastest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version control: Git and GitHub, before there was much to version
&lt;/h2&gt;

&lt;p&gt;I set up Git and pushed to GitHub before I'd really written anything worth backing up. That felt slightly premature at the time, but a few reasons made it worth doing early instead of "later, once there's real code":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cursor makes mistakes, and I make more:&lt;/strong&gt; Since I'm leaning heavily on an AI coding assistant while still learning, I wanted the ability to undo a bad change without panicking, so I could commit often and roll back if needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's the foundation everything else plugs into:&lt;/strong&gt; Automatic deployment (more on that below) needs a repo to deploy &lt;em&gt;from&lt;/em&gt;. Setting it up early meant that piece was just already there when I needed it, instead of being one more thing to configure later under pressure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Habit-forming:&lt;/strong&gt; As a beginner, if I don't build the habit of committing early, I don't build it at all. Starting on day one made it the default instead of an afterthought.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing fancy here, just a repo, sensible commits, and a &lt;code&gt;.gitignore&lt;/code&gt; so generated files and dependencies don't get tracked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosting: why Cloudflare Pages specifically
&lt;/h2&gt;

&lt;p&gt;For hosting, I wanted something that matched the "boring and reliable" philosophy of the rest of the stack. I picked &lt;strong&gt;Cloudflare Pages&lt;/strong&gt; for a few concrete reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Git-connected deploys:&lt;/strong&gt; It builds and deploys straight from my GitHub repo: push to &lt;code&gt;main&lt;/code&gt;, and the live site updates automatically. No manual uploads, no separate deploy step to remember.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built for static/content sites like this one:&lt;/strong&gt; Since Astro outputs mostly static files, Cloudflare Pages is a natural fit, it's designed around exactly that kind of output rather than needing a server running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast by default, globally:&lt;/strong&gt; Cloudflare's network means the site loads quickly regardless of where a visitor is, without me having to configure a CDN separately, which matters for both user experience and page-speed-related SEO signals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generous free tier:&lt;/strong&gt; For a project at this stage, I don't want infrastructure cost to be something I'm thinking about yet. Cloudflare Pages' free tier is enough that I can put that concern aside entirely for now.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The combination, GitHub for version control and Cloudflare Pages for deployment, means my workflow is just: write code, commit, push, and the live site updates on its own a minute later. No manual steps in between to forget or mess up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;With that decision made, the next posts get into the actual build, starting with hosting and workflow decisions, then the tool itself.&lt;/p&gt;

&lt;p&gt;Anyone else gone back and forth between "keep it simple" and "build it for where it's going"? Where do you usually land?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>beginners</category>
      <category>learning</category>
      <category>career</category>
    </item>
    <item>
      <title>From Overthinking to Actually Building Something</title>
      <dc:creator>Iris Khan</dc:creator>
      <pubDate>Fri, 28 Aug 2026 12:39:06 +0000</pubDate>
      <link>https://dev.to/iris_khan_dev/from-overthinking-to-actually-building-something-3ndc</link>
      <guid>https://dev.to/iris_khan_dev/from-overthinking-to-actually-building-something-3ndc</guid>
      <description>&lt;p&gt;I'm Iris, new to web development. A little while back, 2 days ago probably, I wrote about &lt;a href="https://dev.to/iris_khan_dev/i-have-no-idea-what-to-learn-first-and-heres-how-im-deciding-4f4a"&gt;not knowing what to learn first&lt;/a&gt;. The comments on that post were really encouraging, and they pushed me to stop overthinking it and just take action, start building something, even if it's not perfect, even if I don't know everything yet.&lt;/p&gt;

&lt;p&gt;That "something" is Fontly, a free copy-paste font generator. It's not some big, ambitious project. It's small, and I'm just getting started. But it's mine, and it's real progress. This post is an update on where that decision has taken me so far: what I've built, what I've learned, and where it's headed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;If you've ever tried to make your Instagram bio, WhatsApp status, or a social post look a little different, you've probably searched for a "fancy text generator." Most of the ones out there are cluttered with ads, slow to load, or hide the good fonts behind a paywall.&lt;/p&gt;

&lt;p&gt;That's the gap I'm trying to fill with Fontly, a clean, fast, completely free tool where you type your text once and instantly get a bunch of stylish font variations you can copy and paste anywhere. No sign-up, no clutter, no nonsense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I'm at
&lt;/h2&gt;

&lt;p&gt;I'm still early in my web dev journey, so this project is as much a learning exercise as it is a product. Right now I'm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finalizing the site's core functionality&lt;/li&gt;
&lt;li&gt;Working through the basics of clean UI and page speed&lt;/li&gt;
&lt;li&gt;Learning how SEO and discoverability actually work before launch, instead of scrambling after&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Doing the planning work up front, thinking about structure, naming, and how people will actually find the tool, has honestly taught me more than the coding itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's been challenging
&lt;/h2&gt;

&lt;p&gt;A few things I didn't expect to spend so much time on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Naming and branding: landing on "Fontly" took longer than I thought, and I wanted a name that would still make sense if I build more tools later&lt;/li&gt;
&lt;li&gt;Thinking about SEO pre-launch: most guides assume your site is already live; figuring out what to do before that was a bit of trial and error&lt;/li&gt;
&lt;li&gt;Balancing "keep it simple" with "make it actually good": as a beginner it's tempting to either overbuild or underbuild&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  - What's next
&lt;/h2&gt;

&lt;p&gt;I'm continuing to build out Fontly and documenting the process as I go, the wins, the mistakes, all of it. If you're curious or want to follow along, I'll be posting updates here as things progress.&lt;/p&gt;

&lt;p&gt;Would love to hear from anyone who's built a small tool/side-project as a beginner, what do you wish you'd known earlier?&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>discuss</category>
      <category>buildinpublic</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Have No Idea What to Learn First And Here's How I'm Deciding</title>
      <dc:creator>Iris Khan</dc:creator>
      <pubDate>Thu, 27 Aug 2026 07:45:09 +0000</pubDate>
      <link>https://dev.to/iris_khan_dev/i-have-no-idea-what-to-learn-first-and-heres-how-im-deciding-4f4a</link>
      <guid>https://dev.to/iris_khan_dev/i-have-no-idea-what-to-learn-first-and-heres-how-im-deciding-4f4a</guid>
      <description>&lt;p&gt;So I've decided I want to learn web development. Cool. Great. Except then I actually sat down to start and realized... I don't even know what to start with.&lt;/p&gt;

&lt;p&gt;HTML? CSS? JavaScript? Python? React? Back-end stuff? People keep saying different things and every video has a different "you should start with THIS" opinion. It's honestly kind of overwhelming for someone who hasn't written a single line of real code yet.&lt;/p&gt;

&lt;p&gt;So instead of learning anything, what I actually did was open like 15 tabs, watch parts of a bunch of videos, and close my laptop feeling more confused than when I started lol.&lt;/p&gt;

&lt;h2&gt;
  
  
  what I've looked at so far
&lt;/h2&gt;

&lt;p&gt;Not much tbh. A couple YouTube videos comparing front-end and back-end (still not 100% sure I get the difference). One video tried explaining what JavaScript actually does on a page and I understood maybe half of it. And I've been scrolling posts here on DEV a lot, just reading how other people started. That part actually helped the most, not because anyone gave me THE answer, but because everyone started differently and that made me feel less stressed about "picking wrong."&lt;/p&gt;

&lt;h2&gt;
  
  
  how I'm picking
&lt;/h2&gt;

&lt;p&gt;No real system, just vibes basically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML/CSS feels the least scary to start with because you can actually see something change on the screen right away, and that feels good when you're this new to everything&lt;/li&gt;
&lt;li&gt;if a lot of people keep saying "start here" for beginners, I figure there's probably something to it&lt;/li&gt;
&lt;li&gt;and mostly I just need to pick SOMETHING and stop researching forever. I think the real mistake isn't choosing the "wrong" language first, it's never choosing anything at all&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm not trying to get this decision perfect, I just want to actually start building stuff instead of just watching people build stuff.&lt;/p&gt;

&lt;p&gt;so if you've been where I am right now, if you were starting completely over, what would YOU learn first?&lt;/p&gt;

&lt;p&gt;not looking for a "correct" answer since i don't know if there even is one, just curious what actually worked for real people instead of another "top 10 languages to learn in 2026" article lol&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>discuss</category>
      <category>learning</category>
      <category>career</category>
    </item>
    <item>
      <title>10 Free Websites Every Web Developer Should Bookmark</title>
      <dc:creator>Iris Khan</dc:creator>
      <pubDate>Wed, 29 Jul 2026 13:17:49 +0000</pubDate>
      <link>https://dev.to/iris_khan_dev/10-free-websites-every-web-developer-should-bookmark-5fde</link>
      <guid>https://dev.to/iris_khan_dev/10-free-websites-every-web-developer-should-bookmark-5fde</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fszni6o8a2kh9qcsij605.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fszni6o8a2kh9qcsij605.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;10 Free Websites Every Web Developer Should Bookmark&lt;br&gt;
Whether you're just starting your web development journey or have been coding for years, the right tools can save you time and make your workflow much smoother.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here are 10 free websites I think every web developer should know.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. MDN Web Docs
&lt;/h2&gt;

&lt;p&gt;The best place to learn HTML, CSS, and JavaScript. Clear explanations with reliable documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. CodePen
&lt;/h2&gt;

&lt;p&gt;A great playground for testing HTML, CSS, and JavaScript without setting up a project.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Can I Use
&lt;/h2&gt;

&lt;p&gt;Check browser compatibility before using modern CSS or JavaScript features.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. GitHub
&lt;/h2&gt;

&lt;p&gt;Perfect for hosting projects, collaborating with others, and exploring open-source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Regex101
&lt;/h2&gt;

&lt;p&gt;Makes writing and debugging regular expressions much easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. TinyPNG
&lt;/h2&gt;

&lt;p&gt;Compress images to improve website loading speed while maintaining quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Excalidraw
&lt;/h2&gt;

&lt;p&gt;A simple online whiteboard for sketching UI ideas and system designs.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. DevDocs
&lt;/h2&gt;

&lt;p&gt;A fast documentation browser that brings multiple programming docs together.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. JSON Formatter
&lt;/h2&gt;

&lt;p&gt;Useful when working with APIs and debugging JSON responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. CSS-Tricks
&lt;/h2&gt;

&lt;p&gt;An excellent resource for CSS tips, layout techniques, and front-end development articles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;These are some of the free resources I regularly use while learning and building web projects.&lt;/p&gt;

&lt;p&gt;Do you have a favorite developer website that isn't on this list? I'd love to discover more—share it in the comments!&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>tools</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
