A while back I noticed that most “special character” pages are really just giant dumps of glyphs. That works until you mix →, ♥, 😀, and ¯\\_(ツ)_/¯ in the same interface. One of those is a single symbol, one is an emoji, and one is basically a tiny string drawing made out of regular characters.
When I built a copy-paste collection for them, the interesting part wasn't the list itself. It was figuring out how to make one-character symbols and full kaomoji behave like the same kind of data without turning the UI into a mess.
The useful abstraction is just { char, kw }
The core data structure is intentionally boring. Every item becomes an object with the visible text and a keyword slug:
const toSymbols = (list) => list.map(([char, kw]) => ({ char, kw }));
const rawKaomoji = [
["(´・ω・`)", "happy"],
["(╯°□°)╯︵ ┻━┻", "table-flip"],
["¯\\_(ツ)_/¯", "shrug"],
];
const categories = [
{ id: "arrows", symbols: toSymbols(rawArrows) },
{ id: "kaomoji", symbols: toSymbols(rawKaomoji) },
{ id: "emoji", symbols: toSymbols(rawEmoji) },
];
That sounds trivial, but it's the reason the page can treat a right-arrow and a table-flip face with the same button component. Kaomoji aren't a special rich-text format here. They're just strings made from ordinary Unicode characters, stored the same way as everything else.
There's even a comment in the component noting that the symbol content itself doesn't need per-item translation — only the category labels go through i18n. That's a good fit for this kind of dataset: the glyphs are the product, and the UI chrome is what's language-specific.
Search stops caring about categories the moment you type
The page has category tabs, but search switches the mental model from “browse one bucket” to “scan everything”:
const visibleSymbols = computed(() => {
if (isSearching.value) {
const kw = searchKeyword.value.trim().toLowerCase();
const result = [];
categories.forEach((cat) => {
cat.symbols.forEach((item) => {
if (item.kw.indexOf(kw) > -1 || item.char === searchKeyword.value.trim()) {
result.push(item);
}
});
});
return result;
}
return activeCategoryObj.value.symbols;
});
I like this because it's deliberately simple. No extra search index, no special handling per category, no separate kaomoji parser. Every category participates in the same linear scan, and exact-character matching means you can paste a symbol into the box and find that exact entry back.
That simplicity also explains the keyword design. The internal search terms are slugs like arrow-right, heart-outline, and table-flip, so the component can stay small and predictable.
Kaomoji force you to think about layout, not just data
A symbol picker looks easy right up until you remember that some “symbols” are ten-plus characters long. The component handles that with a tiny font-size heuristic:
function symbolFontSize(char) {
const len = (char || "").length;
if (len <= 2) return "26px";
if (len <= 4) return "20px";
if (len <= 8) return "15px";
return "12px";
}
Then each button applies it directly:
<button
class="symbolBtn"
:style="{ fontSize: symbolFontSize(item.char) }"
@click="copySymbol(item.char)"
>
{{ item.char }}
</button>
That keeps arrows and hearts large, while shrinking long kaomoji enough to fit the same grid. The CSS backs it up with word-break: break-word, white-space: normal, and a centered grid cell, which is exactly the kind of boring UI detail that makes this sort of tool feel usable instead of chaotic.
Copying text reliably still needs a fallback path
The copy logic doesn't assume the modern Clipboard API will always work:
function copySymbol(char) {
if (!char) return;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(char).catch(() => fallbackCopy(char));
} else {
fallbackCopy(char);
}
message.success({ content: t("specialCharEmojiCopy.copiedToast") });
addToRecent(char);
}
And the fallback is the old hidden-textarea + document.execCommand("copy") pattern. That isn't glamorous, but it matters for compatibility.
The same section also keeps a recent-history list in localStorage, capped at 20 items and deduplicated by moving the latest copied symbol to the front. It even wraps storage reads/writes in try/catch, which is worth doing because browsers can block localStorage or hand you corrupted data.
Where this still gets weird
A few honest gotchas come with this territory:
- Clipboard writes can still fail depending on browser permissions, context, or legacy API support. The fallback helps, but
execCommand("copy")is still legacy behavior. - The sizing heuristic uses JavaScript string
.length, which counts UTF-16 code units, not visual graphemes. For complex emoji sequences, the “length” the code sees is not always the number a human would expect. - Search is intentionally lightweight: it matches internal keyword slugs or an exact symbol string. That's simple and fast, but it's not the same thing as full multilingual semantic search.
- Rendering is platform-dependent. These are text characters, not images, so the same emoji or symbol can look surprisingly different across fonts and operating systems.
I turned that into a small free tool here: Special Characters & Kaomoji Copy Paste. It ended up being less about “emoji” and more about treating lots of different Unicode text shapes as one copyable interface.
Available in other languages
- 特殊符號與顏文字一鍵複製大全 — 繁體中文
- 特殊符号与颜文字一键复制大全 — 简体中文
- Special Characters & Kaomoji Copy Paste — English
- 特殊記号・顔文字コピペ辞典 — 日本語
- 특수문자·카오모지 복사 모음 — 한국어
- Caractères Spéciaux & Kaomoji à Copier-Coller — Français
- Специальные символы и каомодзи — копировать и вставлять — Русский
- Sonderzeichen & Kaomoji zum Kopieren — Deutsch
- Simbol Spesial & Kaomoji Copy Paste — Bahasa Indonesia
- Caracteres Especiales y Kaomoji para Copiar y Pegar — Español
- Ký Tự Đặc Biệt & Kaomoji Sao Chép Dán — Tiếng Việt
- สัญลักษณ์พิเศษและคาโอโมจิ คัดลอกวาง — ไทย
- Znaki Specjalne i Kaomoji do Kopiowania — Polski
- Özel Karakterler ve Kaomoji Kopyala Yapıştır — Türkçe
- Caratteri Speciali e Kaomoji da Copiare — Italiano
- Caracteres Especiais e Kaomoji para Copiar e Colar — Português
- Speciale Tekens & Kaomoji om te Kopiëren — Nederlands
- Спеціальні символи та каомодзі — копіювати і вставляти — Українська
Top comments (0)