Emoji Picker
The emoji keyboard: search, categories, skin tone and a memory of what you picked last.
Walkthrough
1. Basic
The panel on its own: type to search, arrow around the grid, Enter to take one. It fills whatever width it is given.
Smileys & emotion
People & body
Animals & nature
Food & drink
Activity
Travel & places
Objects
Symbols
Flags
Picked: nothing yet
<script lang="ts"> import { EmojiPicker, Emoji } from 'omaris'; let picked = $state('');</script><div class="flex w-full max-w-sm flex-col gap-3"> <EmojiPicker bind:value={picked} storageKey={null} /> <p class="text-body-sm text-muted-foreground"> Picked: {#if picked} <Emoji emoji={picked} size="lg" label={picked} /> {:else} nothing yet {/if} </p></div> 2. In a composer
The shape it takes in a message box: EmojiButton is the panel behind a trigger — anchored on a pointer, a bottom sheet on a phone.
<script lang="ts"> import { EmojiButton, Input } from 'omaris'; let message = $state('Ship it ');</script><div class="flex w-full max-w-sm items-end gap-2"> <Input class="flex-1" bind:value={message} aria-label="Message" /> <EmojiButton storageKey={null} onselect={(emoji) => (message += emoji)} /></div> 3. Custom trigger
A reaction bar. The trigger is a snippet, so it can be anything; the panel is the same one.
<script lang="ts"> import { EmojiButton, Emoji } from 'omaris'; const QUICK = ['👍', '❤️', '😂', '🎉']; let reaction = $state('👍');</script><div class="flex items-center gap-1.5"> {#each QUICK as emoji (emoji)} <button type="button" class="grid size-9 cursor-pointer place-items-center rounded-full bg-surface-container transition-[scale,background-color] duration-150 ease-spring hover:scale-110 hover:bg-surface-container-high active:scale-95 motion-reduce:transition-none" onclick={() => (reaction = emoji)} aria-label="React with {emoji}" > <Emoji {emoji} size="lg" /> </button> {/each} <EmojiButton storageKey={null} bind:value={reaction} picker={{ height: 220, tones: false }}> {#snippet trigger({ open })} <span class="grid size-9 cursor-pointer place-items-center rounded-full border border-dashed border-border text-muted-foreground transition-[background-color,rotate] duration-200 ease-emphasized hover:bg-surface-container motion-reduce:transition-none" class:rotate-45={open} > <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="size-4"> <path d="M12 5v14M5 12h14" stroke-linecap="round" /> </svg> </span> {/snippet} </EmojiButton> <span class="ms-3 text-body-sm text-muted-foreground"> Reacted with <Emoji emoji={reaction} size="lg" label={reaction} /> </span></div> 4. Skin tone
Skin tone is chosen once, in the preview row, and applies to every emoji that takes one. Bind it when the rest of the app needs to know.
Smileys & emotion
People & body
Animals & nature
Food & drink
Activity
Travel & places
Objects
Symbols
Flags
<script lang="ts"> import { EmojiPicker, Emoji, type EmojiSkinTone } from 'omaris'; let tone = $state<EmojiSkinTone>(3);</script><div class="flex w-full max-w-md flex-wrap items-start gap-6"> <EmojiPicker bind:skinTone={tone} storageKey={null} height={220} class="min-w-64 flex-1" /> <div class="flex flex-col gap-2"> <span class="text-body-sm text-muted-foreground">Tone {tone}</span> <div class="flex gap-2"> <Emoji emoji="👋" {tone} size="xl" /> <Emoji emoji="👍" {tone} size="xl" /> <Emoji emoji="🙏" {tone} size="xl" /> <Emoji emoji="🧑💻" {tone} size="xl" /> </div> </div></div> 5. Emoji
Emoji on its own: the emoji font first, sat on the x-height so it keeps a line's rhythm, and a label only when it carries meaning.
Deployed to production — the emoji is decoration next to text that already says so, so it has no label. A status of its own does: 🟢.
<script lang="ts"> import { Emoji, Text } from 'omaris';</script><div class="flex flex-col gap-4"> <div class="flex items-end gap-3"> <Emoji emoji="🎉" size="xs" /> <Emoji emoji="🎉" size="sm" /> <Emoji emoji="🎉" size="md" /> <Emoji emoji="🎉" size="lg" /> <Emoji emoji="🎉" size="xl" /> <Emoji emoji="🎉" size="2xl" /> </div> <Text class="max-w-md"> Deployed to production <Emoji emoji="🚀" /> — the emoji is decoration next to text that already says so, so it has no label. A status of its own does: <Emoji emoji="🟢" label="Online" />. </Text></div> 6. Overridden
The overridden case: a set of your own, no chrome, a fixed column count, and classes on the heading and the cells.
Status
Smileys & emotion
People & body
Animals & nature
Food & drink
Activity
Travel & places
Objects
Symbols
Flags
<script lang="ts"> import { EmojiPicker, type EmojiGroup } from 'omaris'; /** A status picker, in place of the built-in set. */ const STATUS: EmojiGroup[] = [ { id: 'status', label: 'Status', icon: '🟢', emojis: [ { emoji: '🟢', name: 'available', keywords: ['free'], tone: false, category: 'symbols' }, { emoji: '🟡', name: 'busy', keywords: ['later'], tone: false, category: 'symbols' }, { emoji: '🔴', name: 'do not disturb', keywords: ['dnd'], tone: false, category: 'symbols' }, { emoji: '🌴', name: 'on holiday', keywords: ['away'], tone: false, category: 'nature' }, { emoji: '🏠', name: 'working from home', keywords: ['wfh'], tone: false, category: 'travel' }, { emoji: '🎧', name: 'heads down', keywords: ['focus'], tone: false, category: 'objects' } ] } ];</script><div class="flex w-full flex-wrap items-start gap-6"> <div class="w-56 max-w-full rounded-shape-lg border border-border bg-surface-container-low p-2"> <EmojiPicker bare groups={STATUS} categories={false} preview={false} tones={false} columns={3} height={140} searchPlaceholder="Search status" storageKey={null} class="w-full" /> </div> <EmojiPicker storageKey={null} columns={10} height="12rem" class="w-full max-w-sm" classes={{ heading: 'text-primary', cell: 'rounded-full' }} /></div>