Text vs Emoji Presentation Selectors: VS15 (U+FE0E) and VS16 (U+FE0F)

Text vs Emoji PresentationEmoji Presentation
The default rendering of a character as a colorful emoji glyph, either inherently or when triggered by Variation Selector-16.
Selectors

Many UnicodeUnicode
Universal character encoding standard that assigns a unique number to every character across all writing systems and symbol sets, including emoji.
characters have two visual forms: a monochrome text symbol and a full-color emojiEmoji
A Japanese word (絵文字) meaning 'picture character' — small graphical symbols used in digital communication to express ideas, emotions, and objects.
. 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 PointCode Point
A unique numerical value assigned to each character in the Unicode standard, written in the format U+XXXX (e.g., U+1F600 for 😀).
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
The complete character encoding system maintained by the Unicode Consortium, defining characters, properties, algorithms, and encoding forms.
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
The official Unicode file listing all emoji sequences with their qualification status, code points, and CLDR short names.
file marks each emoji sequenceEmoji Sequence
An ordered set of one or more Unicode code points that together represent a single emoji character.
as:

  • fully-qualified: All VS16 selectors present (e.g., ❤️ = U+2764 + U+FE0F)
  • minimally-qualified: VS16 absent from one or more expected positions
  • unqualifiedUnqualified
    An emoji sequence missing required variation selectors, which may not render as an emoji on all platforms.
    : No variation selectors
# 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)
Unicode characters (VS-15 U+FE0E and VS-16 U+FE0F) that modify whether a character renders in text (monochrome) or emoji (colorful) presentation.
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
An emoji sequence formed by a digit or symbol, followed by VS-16 (U+FE0F) and the combining enclosing keycap character (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

Related Tools

🔀 Platform Compare Platform Compare
Compare how emojis render across Apple, Google, Samsung, Microsoft, and more. See visual differences side by side.
🔍 Sequence Analyzer Sequence Analyzer
Decode ZWJ sequences, skin tone modifiers, keycap sequences, and flag pairs into individual components.

Glossary Terms

Code Point Code Point
A unique numerical value assigned to each character in the Unicode standard, written in the format U+XXXX (e.g., U+1F600 for 😀).
Color Emoji Color Emoji
Full-color emoji rendered using bitmap images or color vector graphics, as opposed to monochrome text-style rendering.
Emoji Emoji
A Japanese word (絵文字) meaning 'picture character' — small graphical symbols used in digital communication to express ideas, emotions, and objects.
Emoji Presentation Emoji Presentation
The default rendering of a character as a colorful emoji glyph, either inherently or when triggered by Variation Selector-16.
Emoji Sequence Emoji Sequence
An ordered set of one or more Unicode code points that together represent a single emoji character.
Keycap Sequence Keycap Sequence
An emoji sequence formed by a digit or symbol, followed by VS-16 (U+FE0F) and the combining enclosing keycap character (U+20E3).
Unicode Unicode
Universal character encoding standard that assigns a unique number to every character across all writing systems and symbol sets, including emoji.
Unicode Standard Unicode Standard
The complete character encoding system maintained by the Unicode Consortium, defining characters, properties, algorithms, and encoding forms.
Unqualified Unqualified
An emoji sequence missing required variation selectors, which may not render as an emoji on all platforms.
Variation Selector (VS) Variation Selector (VS)
Unicode characters (VS-15 U+FE0E and VS-16 U+FE0F) that modify whether a character renders in text (monochrome) or emoji (colorful) presentation.
emoji-test.txt emoji-test.txt
The official Unicode file listing all emoji sequences with their qualification status, code points, and CLDR short names.

Emoji ที่เกี่ยวข้อง

Related Stories