Unicode Blocks Reference
Unicode planes and blocks with their code point ranges, UTF-8 byte cost and the scripts they cover — plus the surrogate, normalisation and security behaviours that catch developers out.
Planes and code points
Unicode defines a code space from U+0000 to U+10FFFF — 1,114,112 possible code points, divided into 17 planes of 65,536 each. As of Unicode 15.1 about 149,800 are assigned, so roughly 87% of the space remains free.
| Plane | Range | Name | Contains |
|---|---|---|---|
| 0 | U+0000–U+FFFF | Basic Multilingual Plane (BMP) | Almost every character in modern use |
| 1 | U+10000–U+1FFFF | Supplementary Multilingual | Emoji, historic scripts, musical notation |
| 2 | U+20000–U+2FFFF | Supplementary Ideographic | Rare CJK characters |
| 3 | U+30000–U+3FFFF | Tertiary Ideographic | Oracle bone script and more CJK |
| 14 | U+E0000–U+EFFFF | Supplementary Special-purpose | Tag characters, variation selectors |
| 15–16 | U+F0000–U+10FFFF | Private Use | Reserved for private agreement |
The BMP/astral distinction matters in code. UTF-16 — used internally by JavaScript, Java and Windows — represents BMP characters in one 16-bit unit and everything above in a surrogate pair. That is why '😀'.length is 2 in JavaScript and why naive string slicing can cut an emoji in half.
Common blocks
A block is a contiguous range reserved for a related set of characters. There are over 300; these are the ones you are most likely to meet.
| From | To | Block | Code points | UTF-8 bytes | Used for |
|---|---|---|---|---|---|
| U+0000 | U+007F | Basic Latin (ASCII) | 128 | 1 | English, digits, punctuation |
| U+0080 | U+00FF | Latin-1 Supplement | 128 | 2 | Western European accents |
| U+0100 | U+017F | Latin Extended-A | 128 | 2 | Central and Eastern European |
| U+0180 | U+024F | Latin Extended-B | 208 | 2 | African and phonetic Latin |
| U+0250 | U+02AF | IPA Extensions | 96 | 2 | Phonetic alphabet |
| U+0370 | U+03FF | Greek and Coptic | 144 | 2 | Greek alphabet |
| U+0400 | U+04FF | Cyrillic | 256 | 2 | Russian, Ukrainian, Bulgarian, Serbian |
| U+0530 | U+058F | Armenian | 96 | 2 | Armenian |
| U+0590 | U+05FF | Hebrew | 112 | 2 | Hebrew, right-to-left |
| U+0600 | U+06FF | Arabic | 256 | 2 | Arabic, right-to-left, contextual shaping |
| U+0900 | U+097F | Devanagari | 128 | 3 | Hindi, Sanskrit, Marathi |
| U+0E00 | U+0E7F | Thai | 128 | 3 | Thai, no word spaces |
| U+1000 | U+109F | Myanmar | 160 | 3 | Burmese |
| U+10A0 | U+10FF | Georgian | 96 | 3 | Georgian |
| U+1E00 | U+1EFF | Latin Extended Additional | 256 | 3 | Vietnamese and more |
| U+2000 | U+206F | General Punctuation | 112 | 3 | Dashes, quotes, spaces |
| U+20A0 | U+20CF | Currency Symbols | 48 | 3 | Euro, rupee, ruble, bitcoin |
| U+2190 | U+21FF | Arrows | 112 | 3 | Directional arrows |
| U+2200 | U+22FF | Mathematical Operators | 256 | 3 | Sum, integral, set theory |
| U+2500 | U+257F | Box Drawing | 128 | 3 | Terminal UI borders |
| U+2600 | U+26FF | Miscellaneous Symbols | 256 | 3 | Weather, zodiac, early emoji |
| U+2700 | U+27BF | Dingbats | 192 | 3 | Check marks, scissors, stars |
| U+3000 | U+303F | CJK Symbols and Punctuation | 64 | 3 | East Asian punctuation |
| U+3040 | U+309F | Hiragana | 96 | 3 | Japanese syllabary |
| U+30A0 | U+30FF | Katakana | 96 | 3 | Japanese syllabary for loanwords |
| U+4E00 | U+9FFF | CJK Unified Ideographs | 20,992 | 3 | Chinese, Japanese kanji, Korean hanja |
| U+AC00 | U+D7AF | Hangul Syllables | 11,184 | 3 | Korean |
| U+D800 | U+DFFF | Surrogates | 2,048 | n/a | UTF-16 mechanism, never valid alone |
| U+E000 | U+F8FF | Private Use Area | 6,400 | 3 | Icon fonts, vendor glyphs |
| U+FB00 | U+FB4F | Alphabetic Presentation Forms | 80 | 3 | Ligatures such as fi |
| U+FE00 | U+FE0F | Variation Selectors | 16 | 3 | Emoji vs text presentation |
| U+FFF0 | U+FFFF | Specials | 16 | 3 | Replacement character U+FFFD |
| U+1D400 | U+1D7FF | Mathematical Alphanumerics | 1,024 | 4 | Styled maths letters |
| U+1F300 | U+1F5FF | Misc Symbols and Pictographs | 768 | 4 | Most emoji |
| U+1F600 | U+1F64F | Emoticons | 80 | 4 | Face emoji |
| U+1F680 | U+1F6FF | Transport and Map | 128 | 4 | Vehicles, signs |
| U+1F900 | U+1F9FF | Supplemental Symbols | 256 | 4 | Newer emoji |
| U+20000 | U+2A6DF | CJK Extension B | 42,720 | 4 | Rare Chinese characters |
How UTF-8 encodes each range
UTF-8 is a variable-width encoding. The number of bytes follows directly from the code point value:
| Code points | Bytes | Bit pattern |
|---|---|---|
| U+0000–U+007F | 1 | 0xxxxxxx |
| U+0080–U+07FF | 2 | 110xxxxx 10xxxxxx |
| U+0800–U+FFFF | 3 | 1110xxxx 10xxxxxx 10xxxxxx |
| U+10000–U+10FFFF | 4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
The design is self-synchronising: a leading byte always starts with a distinctive prefix and continuation bytes always start with 10. Land anywhere in a UTF-8 stream and you can find the next character boundary by scanning forward for a byte that does not start with 10. That property is why UTF-8 survives truncation and packet loss so much better than UTF-16.
Practical consequence: English text costs 1 byte per character, European accented text 2, CJK 3, emoji 4. A CJK page is roughly three times the bytes of the same content in English.
Surrogates, combining marks and normalisation
Surrogates (U+D800–U+DFFF) are not characters. They exist solely as the UTF-16 mechanism for encoding astral code points and are permanently unassigned in Unicode. A lone surrogate in a string is invalid and will not encode to UTF-8.
Combining marks mean one visible glyph can be several code points. é can be U+00E9 (a single precomposed character) or U+0065 U+0301 (e followed by a combining acute accent). They look identical and compare unequal.
Normalisation resolves this. Four forms exist; two matter:
- NFC composes where possible. This is what you want for storage, comparison and URLs, and what the W3C recommends for the web.
- NFD decomposes. Useful for stripping accents: decompose, then remove the combining marks.
'caf\u00e9' === 'cafe\u0301' // false
'caf\u00e9'.normalize('NFC') === 'cafe\u0301'.normalize('NFC') // true
// strip accents
s.normalize('NFD').replace(/\p{Diacritic}/gu, '')Always normalise to NFC before comparing user input, storing usernames or generating slugs. The slug generator does this as part of its transliteration step.
Confusables and homograph attacks
Unicode contains many characters that look identical to Latin letters. Cyrillic а (U+0430) is visually indistinguishable from Latin a (U+0061) in most fonts. Register аpple.com and it displays as apple.com.
Defences that actually work:
- Browsers show Punycode (
xn--pple-43d.com) when a domain mixes scripts, which is the main mitigation. - Restrict usernames and identifiers to a single script, or to a defined allowed set.
- Apply the Unicode confusable-skeleton algorithm from UTS #39 when uniqueness matters.
- Reject zero-width characters (U+200B–U+200D) and bidirectional overrides (U+202A–U+202E) in identifiers. The 2021 “Trojan Source” research showed bidi overrides can make source code read differently to a human than to a compiler.
Frequently Asked Questions
How many characters does Unicode have?
Unicode 15.1 defines about 149,800 assigned code points out of a possible 1,114,112. Roughly 87% of the code space is still unassigned.
What is the Basic Multilingual Plane?
Plane 0, U+0000 to U+FFFF, containing almost every character in modern everyday use. Characters above it are called astral or supplementary.
Why is emoji length 2 in JavaScript?
JavaScript strings are UTF-16. Emoji live above U+FFFF and need a surrogate pair — two 16-bit units. Use [...str] or Intl.Segmenter to count actual characters.
How many bytes does a character take in UTF-8?
One for ASCII, two for most European scripts, three for CJK and most other scripts, and four for emoji and astral characters.
What is Unicode normalisation?
Converting text to a canonical form so that visually identical strings compare equal. NFC composes characters and is the right default for storage and comparison.
What are surrogates?
Code points U+D800 to U+DFFF, reserved as the UTF-16 mechanism for encoding astral characters. They are never valid characters on their own.
What is a homograph attack?
Using visually identical characters from different scripts to impersonate a name or domain, such as Cyrillic а in place of Latin a. Browsers mitigate it by displaying Punycode for mixed-script domains.
Sources & further reading
- The Unicode Standard — the authoritative specification and code charts
- UAX #15: Normalization Forms — the definition of NFC, NFD, NFKC and NFKD
- UTS #39: Security Mechanisms — confusable detection and identifier restrictions
- Trojan Source — bidirectional override attacks on source code