Text vs Emoji PresentationEmoji Presentation
Cách hiển thị mặc định của một ký tự dưới dạng glyph emoji có màu, hoặc vốn có hoặc được kích hoạt bởi Variation Selector-16. Selectors
Many UnicodeUnicode
Tiêu chuẩn mã hóa ký tự phổ quát gán một số duy nhất cho mỗi ký tự trong tất cả hệ thống chữ viết và bộ ký hiệu, bao gồm cả emoji. characters have two visual forms: a monochrome text symbol and a full-color emojiEmoji
Từ tiếng Nhật (絵文字) có nghĩa là 'ký tự hình ảnh' — các ký hiệu đồ họa nhỏ dùng trong giao tiếp kỹ thuật số để diễn đạt ý tưởng, cảm xúc và sự vật.. The character ☎ (U+260E BLACK TELEPHONE) can appear as ☎︎ (text, black outline) or ☎️ (emoji, with color). The choice is controlled by variation selectors — invisible Unicode code points that follow the base character.
Understanding variation selectors is essential for consistent rendering, correct text processing, and accurate emoji detection.
The Two Emoji Variation Selectors
| Selector | Code Point | Name | Effect |
|---|---|---|---|
| VS15 | U+FE0E | VARIATION SELECTOR-15 | Forces text presentation |
| VS16 | U+FE0F | VARIATION SELECTOR-16 | Forces emoji presentation |
These selectors are combining characters — they apply to the immediately preceding base character and are not displayed themselves.
Examples
# Python
text_phone = "\u260E\uFE0E" # ☎ + VS15 → ☎︎ (text)
emoji_phone = "\u260E\uFE0F" # ☎ + VS16 → ☎️ (emoji)
default_phone = "\u260E" # ☎ → ☎ (platform default: text)
# These look different in supporting renderers:
print(text_phone) # ☎︎ monochrome
print(emoji_phone) # ☎️ colored
print(default_phone) # ☎ (depends on platform)
const textPhone = "\u260E\uFE0E"; // ☎︎
const emojiPhone = "\u260E\uFE0F"; // ☎️
// Note: these are different strings
console.log(textPhone === emojiPhone); // false
console.log(textPhone.length); // 2
console.log(emojiPhone.length); // 2
Which Characters Have Dual Presentations?
The Unicode StandardUnicode Standard
Hệ thống mã hóa ký tự đầy đủ do Unicode Consortium duy trì, định nghĩa các ký tự, thuộc tính, thuật toán và dạng mã hóa. defines an Emoji_Presentation property and a separate table of characters that support both text and emoji presentation — characters that are Emoji=Yes but Emoji_Presentation=No. These are text-default emoji: they render as text symbols unless followed by VS16.
Common text-default emoji include:
| Character | Code Point | Default | With VS16 |
|---|---|---|---|
| ☎ | U+260E | ☎ text | ☎️ emoji |
| ⌚ | U+231A | ⌚ text | ⌚️ emoji |
| ✂ | U+2702 | ✂ text | ✂️ emoji |
| ❤ | U+2764 | ❤ text | ❤️ emoji |
| ✈ | U+2708 | ✈ text | ✈️ emoji |
| ☀ | U+2600 | ☀ text | ☀️ emoji |
| © | U+00A9 | © text | ©️ emoji |
| ® | U+00AE | ® text | ®️ emoji |
| ‼ | U+203C | ‼ text | ‼️ emoji |
The full list is in emoji-variation-sequences.txt in the Unicode UCD.
Fully-Qualified vs Minimally-Qualified Emoji
The Unicode standard defines fully-qualified emoji as those with all required variation selectors present. The emoji-test.txtemoji-test.txt file marks each emoji sequenceEmoji Sequence
Tệp Unicode chính thức liệt kê tất cả chuỗi emoji cùng trạng thái đủ điều kiện, điểm mã và tên ngắn CLDR của chúng.
Một tập hợp có thứ tự gồm một hoặc nhiều điểm mã Unicode cùng nhau đại diện cho một ký tự emoji duy nhất. as:
fully-qualified: All VS16 selectors present (e.g., ❤️ = U+2764 + U+FE0F)minimally-qualified: VS16 absent from one or more expected positionsunqualifiedUnqualified: No variation selectors
Chuỗi emoji thiếu các variation selector bắt buộc, có thể không hiển thị như emoji trên tất cả các nền tảng.
# From emoji-test.txt:
2764 FE0F ; fully-qualified # ❤️ E0.6 red heart
2764 ; unqualified # ❤ E0.6 red heart
For reliable emoji lookup and comparison, normalize all emoji to their fully-qualified form.
Detecting and Normalizing Variation Selectors
Strip All Variation Selectors (Python)
import regex
VS15 = "\uFE0E"
VS16 = "\uFE0F"
def strip_variation_selectors(text: str) -> str:
"""Remove VS15 and VS16 from text."""
return text.replace(VS15, "").replace(VS16, "")
def normalize_to_emoji(text: str) -> str:
"""
Convert text-default emoji to emoji presentation
where a VS16 form exists.
"""
# Strip any existing selectors first
clean = strip_variation_selectors(text)
# Add VS16 after each character that has an emoji presentation
result = []
for char in clean:
result.append(char)
# Check if this character has an emoji VS16 form
if has_emoji_presentation(char):
result.append(VS16)
return "".join(result)
def has_emoji_presentation(char: str) -> bool:
"""Return True if char has a VS16 emoji presentation form."""
TEXT_DEFAULT_EMOJI = {
"\u00A9", "\u00AE", "\u203C", "\u2049", "\u2122",
"\u2139", "\u2194", "\u2195", "\u2196", "\u2197",
"\u2198", "\u2199", "\u21A9", "\u21AA", "\u231A",
"\u231B", "\u2328", "\u23CF", "\u260E", "\u2611",
"\u2614", "\u2615", "\u2618", "\u261D", "\u2620",
"\u2622", "\u2623", "\u2626", "\u262A", "\u262E",
"\u262F", "\u2638", "\u2639", "\u263A", "\u2640",
"\u2642", "\u2648", "\u2702", "\u2708", "\u2764",
# ... full list from emoji-variation-sequences.txt
}
return char in TEXT_DEFAULT_EMOJI
# Examples
print(strip_variation_selectors("❤️")) # ❤ (just U+2764)
print(strip_variation_selectors("☎︎")) # ☎ (just U+260E)
print(normalize_to_emoji("❤")) # ❤️ (adds VS16)
JavaScript Implementation
const VS15 = '\uFE0E';
const VS16 = '\uFE0F';
function stripVariationSelectors(str) {
return str.replace(/[\uFE0E\uFE0F]/g, '');
}
function getVariationSelectorType(char) {
if (char === VS15) return 'text';
if (char === VS16) return 'emoji';
return null;
}
// Check if a string position has a variation selectorVariation Selector (VS)
Các ký tự Unicode (VS-15 U+FE0E và VS-16 U+FE0F) xác định xem một ký tự được hiển thị dưới dạng văn bản (đơn sắc) hay emoji (có màu). following it
function getPresentation(text, index) {
const nextChar = text[index + 1];
if (nextChar === VS16) return 'emoji';
if (nextChar === VS15) return 'text';
// No selector — check Emoji_Presentation property
const char = String.fromCodePoint(text.codePointAt(index));
return /^\p{Emoji_Presentation}$/u.test(char) ? 'emoji' : 'text';
}
// Normalize: ensure emoji presentation for emoji-capable characters
const TEXT_DEFAULT_EMOJI = new Set([
'\u00A9', '\u00AE', '\u203C', '\u2122', '\u2139',
'\u260E', '\u2702', '\u2708', '\u2764',
// ... full set
]);
function ensureEmojiPresentation(text) {
let result = '';
for (const char of text) {
result += char;
if (char !== VS15 && char !== VS16 && TEXT_DEFAULT_EMOJI.has(char)) {
result += VS16;
}
}
return result;
}
Impact on String Comparison
Variation selectors make otherwise-identical-looking strings unequal:
heart_no_vs = "❤" # U+2764
heart_vs16 = "❤️" # U+2764 + U+FE0F
print(heart_no_vs == heart_vs16) # False
print(len(heart_no_vs)) # 1
print(len(heart_vs16)) # 2
# For comparison purposes, normalize first:
def normalize_emoji_for_compare(text: str) -> str:
return strip_variation_selectors(text)
print(normalize_emoji_for_compare("❤️") == normalize_emoji_for_compare("❤")) # True
This matters for:
- Database lookups: Searching for ❤ won't match ❤️ stored with VS16
- Emoji counting: ❤ and ❤️ should count as the same emoji
- Deduplication: Two users reacting with ❤ and ❤️ should be merged
Variation Selectors in Keycap Sequences
Digit emoji (1️⃣ through 9️⃣, 0️⃣, #️⃣, *️⃣) are three-code-point sequences:
1️⃣ = U+0031 (digit "1") + U+FE0F (VS16) + U+20E3 (combining enclosing keycap)
The VS16 here is mandatory — without it, the keycap sequenceKeycap Sequence
Một chuỗi emoji được tạo thành từ chữ số hoặc ký hiệu, theo sau là VS-16 (U+FE0F) và ký tự keycap bao quanh (U+20E3). is malformed in many renderers:
keycap_correct = "1\uFE0F\u20E3" # 1️⃣ — correct
keycap_malformed = "1\u20E3" # 1⃣ — may not render correctly
# Fully-qualified check
import emoji
print(emoji.is_emoji(keycap_correct)) # True
print(emoji.is_emoji(keycap_malformed)) # Varies by library version
CSS and HTML Implications
In HTML, browsers generally handle variation selectors automatically. However, when you construct text dynamically via JavaScript or server-side templates, you must include VS16 explicitly for text-default emoji:
<!-- These may render differently depending on browser/OS -->
<span>❤</span> <!-- text form — black outline on some platforms -->
<span>❤️</span> <!-- emoji form — red heart everywhere -->
<!-- In JavaScript templates -->
const heartEmoji = "\u2764\uFE0F"; // Always emoji presentation
element.textContent = `I ${heartEmoji} JavaScript`;
Explore More on EmojiFYI
- See the raw code points including variation selectors: Sequence Analyzer
- Compare presentation across platforms: Compare Tool
- Unicode emoji terminology reference: Glossary
- Programmatic access to presentation data: API Reference