Reactions

Run code when a user reacts to a card you posted, using JSX.


Attach onReaction to a message you post, and your handler runs when someone reacts to that message. Good for a thumbs-up to confirm, or a refresh emoji to redraw a card.

This is the per-card handler. It builds on the UI library, so set up JSX first. To react to any message in the channel, see global reactions.

A card that reacts#

import { Message, Section, type ChannelNode } from "@copilotkit/channels/ui";

export function PollCard(): ChannelNode {
  return (
    <Message
      onReaction={async (emoji, reaction) => {
        if (!reaction.added) return; // ignore un-reactions
        if (emoji !== "thumbs_up") return;
        await reaction.thread.post(`Thanks for the vote, ${reaction.user?.name ?? "friend"}.`);
      }}
    >
      <Section>{"React with a thumbs up to vote."}</Section>
    </Message>
  );
}

Post it, and register it so a late reaction still resolves after a restart:

const channel = createChannel({
  name: "poll-bot",
  agent,
  components: [PollCard],
});

await thread.post(<PollCard />);

What the handler gets#

onReaction={(emoji, reaction) => {
  emoji;              // canonical name: "thumbs_up", "refresh", "fire", ...
  reaction.rawEmoji;  // the platform's native token
  reaction.added;     // true = added, false = removed
  reaction.user;      // who reacted
  reaction.thread;    // post back here
  reaction.messageRef; // pass to thread.update(...) to redraw this card
}}

Emoji names are normalized#

The same reaction arrives under one canonical emoji name on every platform. The counterclockwise-arrows emoji is "refresh" whether it came in as Slack's arrows_counterclockwise, a Teams token, or the raw unicode elsewhere. So you match on the name once:

if (emoji === "refresh") {
  await reaction.thread.update(reaction.messageRef, <PollCard />);
}

Reach for reaction.rawEmoji only when you need the exact platform token, for example a custom Slack emoji that has no canonical name.

Emoji helpers#

@copilotkit/channels/ui exports a few helpers for working with emoji names.

Use the emoji accessor instead of a bare string so a typo is a compile error and you get autocomplete:

import { emoji } from "@copilotkit/channels/ui";

if (reaction.emoji === emoji.refresh) {
  // ...
}

The known names come from EMOJI_TABLE (thumbs_up, heart, fire, eyes, bug, check, cross, tada, rocket, refresh, and more). The EmojiValue type is KnownEmoji | (string & {}), so a known name autocompletes while any custom token still passes through.

HelperSignatureUse
toCanonicalEmoji(value: EmojiValue) => EmojiValueResolve a name, Slack shortcode, or unicode to the canonical name. Unknown tokens pass through.
normalizeEmoji(token: string, platform) => EmojiValue | undefinedA platform-native token to its canonical name, or undefined if unknown.
toPlatformEmoji(value: EmojiValue, platform) => string | undefinedThe reverse: a canonical name to a platform's native token.

platform is one of "slack" | "discord" | "telegram" | "teams" | "whatsapp".

import { toCanonicalEmoji, toPlatformEmoji } from "@copilotkit/channels/ui";

toCanonicalEmoji("arrows_counterclockwise"); // "refresh"
toCanonicalEmoji("🔄");                       // "refresh"
toPlatformEmoji("refresh", "slack");          // "arrows_counterclockwise"

Ingress already normalizes reaction.emoji for you, so you mostly reach for these when matching a caller-supplied filter or posting a reaction with thread.react on a specific platform.