One emoji from an image catalogue, drawn as an <img> the size of the text around it, named by its alt (the emoji's name). An animated emoji shows its still image until it plays: while the mouse is on it or on the button around it, or that button has focus, or while play says so. Its animation is fetched once the still image has loaded, at low priority, so the first play starts at once; not for a still given fetchPriority="low", nor while the browser asks to save data. With reduced motion it always shows the still image, and the animation is not fetched. skinTone draws an emoji with tones in one of them, and placeholder stands in until its picture has loaded. Its picture is fetched as soon as it is drawn, not when it nears the screen, so an emoji in an older message is there when the reader scrolls back to it. Its box is the text's size from the first frame, and a picture that fails to load keeps it, so no line moves as emoji load or fail.
Install
Once per app, with Tailwind CSS v4 and an @/* path alias (Ouro HX is built on Base UI, so --base base; skip -t in an existing app):
npx shadcn@latest init --base base --preset nova --yes --no-monoreponpx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.3.10Then the component. From v0.8.0 it installs only the helpers it imports and never touches your stylesheet (Installing):
npx shadcn@latest add ourostack/ouro-ui/emoji#v1.3.10Or, with the @ouro namespace set up:
npx shadcn@latest add @ouro/ouro-base @ouro/emojiIts tests, stories and Playwright spec are optional. They need Vitest 4.1.11 or later, and from v0.8.0 never change a version your app has (Testing):
npx shadcn@latest add ourostack/ouro-ui/emoji#v1.3.10 ourostack/ouro-ui/emoji-tests#v1.3.10import { Emoji } from "@/components/ouro/emoji/emoji";Paths use shadcn's default @/ alias; the CLI rewrites them to yours.
Example
<Emoji item={party} />
<Emoji item={party} placeholder="/emoji/waiting.svg" />
<Button variant="plain" aria-label="Party popper"><Emoji item={party} alt="" /></Button>Use instead when
- Use
EmojiTextinstead to draw the emoji in a message's text, and an emoji character for an app that uses the platform's emoji font.
Props
Type: EmojiProps.
alt- Type:
string - Default: the emoji's
name - The image's text alternative.
className- Type:
string - Layout only: margins, and a size other than the text's (
size-8). Appearance classes warn in development. item(required)- Type:
EmojiItem - The emoji to draw, from the app's catalogue.
placeholder- Type:
string | ReactElement - Shows in the emoji's place until its picture has loaded and decoded: an image's address, or an element (centred in the emoji's box, which it does not change). The picture then fades in over it, at once with reduced motion (the device's setting, or OuroProvider's
motion). The placeholder is hidden from screen readers, so the emoji keeps its name, and a picture that fails to load or decode leaves it showing. It shows again for a new picture (another emoji, tone orsrc), not while an animated emoji plays. With it, the<img>sits in a<span>that takesclassName. play- Type:
boolean - Plays an animated emoji now (
true) or keeps it still (false). Without it, the emoji plays while the mouse is on it or on the button, link or option around it, or that has focus. Reduced motion keeps it still whatever this says. ref- Type:
Ref<HTMLImageElement> - The
<img>. skinTone- Type:
"default" | "light" | "medium-light" | "medium" | "medium-dark" | "dark" - Default:
"default" - The skin tone to draw it in, for an emoji with
skinTones.
Native attributes
Also accepts the attributes in React's HTMLAttributes and ImgHTMLAttributes (id, aria-*, event handlers), passed to the rendered element, except those named on this page.
Don't use
These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.
animate: Not a prop. Ouro UI usesplay:<Emoji item={item} play />.emoji: Not a prop. Emoji draws a catalogue's item, not text:<Emoji item={catalogueItem} />.src: Not a prop. The image comes from the catalogue's item:<Emoji item={{ id, name, src }} />.
className is for layout only
Pass margins, width and grid placement (mt-4, w-full, col-span-2). Your classes always win over Ouro UI's: Emoji merges className last with cn (tailwind-merge), so a class that conflicts with its own replaces it.layout-classes
Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (hover:, sm:) and any ! are removed, it starts with bg-, text-, border-, rounded-, shadow-, font-, ring-, from-, via-, to-, fill- or stroke-, or is border, rounded, shadow or ring, or has a dark: variant. Text wrapping, overflow and table layout classes (text-wrap, text-ellipsis, border-collapse) are layout and do not warn.
Alignment classes (text-center) warn too: set alignment on a parent element.
Types
EmojiFilmstrip
An animation drawn as a filmstrip: square frames stacked top to bottom in one image, so the frames are the image's height over its width.
interface EmojiFilmstrip {
/** The filmstrip image's address. */
src: string;
/** Frames a second. */
fps: number;
}EmojiItem
One emoji in an image catalogue: its id, its name and the image that draws it. An app maps its own emoji set to this, whether a standard set or an organization's custom emoji.
interface EmojiItem {
/** Identifies the emoji, unique in the catalogue: `"party"`. Reactions, recents and `:id:` tokens use it. */
id: string;
/** Its name, which is the image's `alt`, what screen readers hear and what the search matches first: `"Party popper"`. */
name: string;
/**
* The still image's address. It shows whenever the emoji is not playing, and always with reduced motion.
*/
src: string;
/** Sizes of the still image for sharper screens, as `<img srcset>`: `"party-2x.png 2x"`. */
srcSet?: string | undefined;
/**
* An animated image (GIF, animated WebP or PNG), which plays while the pointer is on the emoji
* or on the button around it, or that button has focus. With reduced motion it never plays.
*/
animatedSrc?: string | undefined;
/**
* The animation as a filmstrip, square frames stacked top to bottom in one image, for an
* emoji without `animatedSrc`: while the emoji plays, it steps through the frames at `fps`;
* otherwise, and always with reduced motion, `src` shows.
*/
filmstrip?: EmojiFilmstrip | undefined;
/** The still image's width in pixels, given to the `<img>` so the page keeps its shape while it loads. It shows at the text's size. */
width?: number | undefined;
/** The still image's height in pixels, as `width`. */
height?: number | undefined;
/** Short names typed between colons, without them: `["party", "tada"]`. The first makes the emoji's token, `:party:`. */
shortcodes?: readonly string[] | undefined;
/** More words the search matches: `["celebrate", "hooray"]`. */
keywords?: readonly string[] | undefined;
/**
* The emoji as plain text, such as its Unicode character (`"๐"`), for places an image cannot
* go (a copied message, a notification). `EmojiText` finds it in messages and draws the image;
* Ouro never draws it as text.
*/
text?: string | undefined;
/** It is the organization's or app's own emoji rather than one of the standard set. Ouro draws it the same; apps can label or treat it apart. */
custom?: boolean | undefined;
/**
* The emoji in Unicode's five skin tones, light to dark; leave it out for an emoji without tones.
*/
skinTones?: readonly EmojiItemTone[] | undefined;
}EmojiItemTone
An emoji's image in one skin tone: skinTones on an EmojiItem.
interface EmojiItemTone {
/** Identifies the emoji in this tone, unique in the catalogue: `"thumbs-up-tone3"`. Reactions and tokens use it. */
id: string;
/** The still image's address. */
src: string;
/** Sizes of the still image for sharper screens, as `<img srcset>`: `"wave-2x.png 2x"`. */
srcSet?: string | undefined;
/** An animated image (GIF, animated WebP or PNG), shown while the emoji plays; the still one shows otherwise. */
animatedSrc?: string | undefined;
/** The animation as a filmstrip of square frames, played while the emoji plays, for a tone without `animatedSrc`. */
filmstrip?: EmojiFilmstrip | undefined;
/**
* The emoji in this tone as text (`"๐๐ฝ"`), which `EmojiText` finds in messages; Ouro never draws it as text.
*/
text?: string | undefined;
}Claim sources
- layout-classes: test:registry/ouro/components/ouro/emoji/emoji.test.tsx::Emoji > passes the img attributes and ref through, and lets app layout classes win
verify:testExists(registry/ouro/components/ouro/emoji/emoji.test.tsx::Emoji > passes the img attributes and ref through, and lets app layout classes win)
