Resolvers and mentions

Slack payloads reference workspace entities by ID: <@U024BE7LH> in mrkdwn, { type: "user", user_id: "U024BE7LH" } in rich text. The renderer has no workspace access, so by default it shows the ID (or the payload's own fallback label). The resolvers prop maps IDs to display values without changing the payload:

import { BlockKit, type BlockKitInput } from "react-blockkit";

const users: Record<string, string> = { U024BE7LH: "Maya Ito" };
const channels: Record<string, string> = { C024BE91L: "release-train" };
const groups: Record<string, string> = { S0614TZR7: "on-call" };

export function Preview({ data }: { data: BlockKitInput }) {
  return (
    <BlockKit
      data={data}
      resolvers={{
        user: (id) => users[id],
        channel: (id) => channels[id],
        usergroup: (id) => groups[id],
      }}
    />
  );
}

Resolvers apply to mentions in both mrkdwn text and rich_text blocks, and may return any React node: a styled chip, an avatar and name, plain text. Returning undefined falls through to the default rendering for that mention.

Available resolvers

ResolverSignatureResolves
user(id: string) => ReactNode | undefined<@U…> and rich-text user elements
channel(id: string) => ReactNode | undefined<#C…> and rich-text channel elements
usergroup(id: string) => ReactNode | undefined<!subteam^S…> and rich-text usergroup elements
team(id: string) => ReactNode | undefinedRich-text team elements
emoji(name: string, unicode?: string) => ReactNode | undefined:shortcode: tokens and rich-text emoji elements
date(timestamp: number, format: string) => ReactNode | undefined<!date^…> tokens and rich-text date elements

The renderer always supplies the mention sigil itself (@ for users and groups, # for channels), so return just the name.

Fallback order

Each mention kind resolves through a fixed chain; the first defined value wins.

  • Users, channels, usergroups: your resolver → the payload's fallback label (the part after | in <@U024BE7LH|maya>) → the raw ID.
  • Emoji: your resolver → the payload's explicit unicode code point (rich text only) → a built-in glyph table of 36 shortcodes, the set slackmark emits (:tada:, :warning:, :white_check_mark:, and similar) → the literal :name: text. Unknown and custom emoji names always stay readable. That table is a floor, not full Slack emoji coverage, so supply an emoji resolver if you are rendering arbitrary workspace payloads.
  • Dates: your resolver → deterministic UTC formatting of Slack's date tokens ({date}, {date_num}, {date_short}, {time}, and variants) → the payload's fallback text for relative or unrecognized tokens ({ago} needs a clock, so it intentionally uses the fallback) → an ISO date.

Broadcast mentions (<!here>, <!channel>, <!everyone>) render as styled @here/@channel/@everyone text and take no resolver.

Example

This message renders with a demo directory wired into user, channel, and usergroup; emoji and the date token use the built-in fallbacks:

with resolvers
@Maya Ito merged the release branch into #release-train 🎉 cc @on-call. Freeze starts Jul 23, 2026.
View payload JSON
{
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "<@U024BE7LH> merged the release branch into <#C024BE91L> :tada:\ncc <!subteam^S0614TZR7>. Freeze starts <!date^1784764800^{date_short}|Jul 23, 2026>."
      }
    }
  ]
}
no resolvers
@U024BE7LH merged the release branch into #C024BE91L 🎉 cc @S0614TZR7. Freeze starts Jul 23, 2026.
View payload JSON
{
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "<@U024BE7LH> merged the release branch into <#C024BE91L> :tada:\ncc <!subteam^S0614TZR7>. Freeze starts <!date^1784764800^{date_short}|Jul 23, 2026>."
      }
    }
  ]
}

The right-hand render is the same payload with no resolvers at all. Each mention falls back to its payload label or raw ID, and nothing disappears.