slack-blocks-to-jsx
v1.0.4
Published
A library to convert Slack blocks to JSX components to be used with React
Maintainers
Readme
Slack Blocks to JSX
A React library that renders Slack Block Kit components as JSX with pixel-perfect styling. Full parity with Slack's Block Kit specification.
🎮 Live Playground | 📖 Blog Post | 📦 NPM
Table of Contents
- Features
- Installation
- Quick Start
- Supported Blocks
- Supported Elements
- Rich Text Support
- Dark Mode
- Hooks API
- Data Props
- TypeScript
- Theming & Customization
- FAQ
- Contributing
- Support
- License
Features
- Full Block Kit parity - Supports all Slack Block Kit block types, elements, and composition objects
- Pixel-perfect rendering - Matches Slack's native appearance
- Dark mode - Built-in dark mode via
data-theme="dark"attribute - Rich text support - Bold, italic, strikethrough, code, links, colors, and more
- Standard markdown block - Renders standard markdown (tables, task lists, syntax highlighting) via
react-markdown - Interactive elements - Buttons, select menus, date/time pickers, radio buttons, checkboxes, overflow menus, and more
- Confirm dialogs - Built-in confirmation dialog support on interactive elements
- Mentions - User, channel, and usergroup mentions with custom rendering
- Emoji support - Full emoji rendering with skin tone variants
- Date formatting - Slack date syntax support
- Custom hooks - Override rendering for any element type
- TypeScript first - Full type exports for type safety
- Themeable - CSS class overrides for custom styling
- React 17, 18, 19 - Compatible with all modern React versions
Installation
npm install slack-blocks-to-jsxOr using yarn:
yarn add slack-blocks-to-jsxOr using pnpm:
pnpm add slack-blocks-to-jsxNext.js / SSR Setup
Since react-markdown and remark-gfm are ESM-only packages, Next.js users need to add this to their next.config.js / next.config.mjs:
const nextConfig = {
experimental: {
esmExternals: "loose",
},
transpilePackages: ["slack-blocks-to-jsx", "react-markdown", "remark-gfm"],
};Quick Start
1. Import the styles
import "slack-blocks-to-jsx/dist/style.css";2. Use the Message component
import { Message } from "slack-blocks-to-jsx";
const blocks = [
{
type: "section",
text: {
type: "mrkdwn",
text: "Hello, *world*! :wave:",
},
},
];
function App() {
return <Message blocks={blocks} name="My App" logo="/logo.png" time={new Date()} />;
}3. Add type safety (optional)
import type { Block } from "slack-blocks-to-jsx";
const blocks: Block[] = [
// TypeScript will validate your blocks
];Supported Blocks
| Block Type | Status | Notes |
| --------------- | ------- | ------------------------------------------------------------ |
| Section | ✅ Full | Text, fields, accessories, expand property |
| Divider | ✅ Full | Horizontal divider |
| Image | ✅ Full | Collapsible with alt text, slack_file support |
| Context | ✅ Full | Images and text elements |
| Header | ✅ Full | Large bold text |
| Rich Text | ✅ Full | Lists, quotes, preformatted, sections, color elements |
| Video | ✅ Full | Collapsible video embed |
| Table | ✅ Full | Rows, columns, alignment |
| Actions | ✅ Full | All interactive element types supported |
| Input | ✅ Full | All input element types supported |
| File | ✅ Full | Remote file display |
| Context Actions | ✅ Full | Feedback buttons and icon buttons |
| Markdown | ✅ Full | Standard markdown with GFM (tables, task lists, code blocks) |
| Plan | ✅ Full | Sequential task display with status indicators |
| Task Card | ✅ Full | Individual task with title, details, output, sources, status |
Supported Elements
Interactive Elements
| Element | Status | Notes | | ---------------- | ------- | --------------------------------------------------- | | Button | ✅ Full | Primary, danger, default styles with confirm dialog | | Workflow Button | ✅ Full | Triggers a workflow on click | | Overflow Menu | ✅ Full | Three-dot menu with options and confirm dialog | | Radio Buttons | ✅ Full | Radio button group with initial selection | | Checkboxes | ✅ Full | Multi-select with descriptions and confirm dialog | | Feedback Buttons | ✅ Full | Positive/negative sentiment buttons | | Icon Button | ✅ Full | Icon-based action button |
Select Menus
| Element | Status | Notes | | -------------------- | ------- | --------------------------------------------------------- | | Static Select | ✅ Full | Searchable dropdown with option groups and confirm dialog | | External Select | ✅ Full | External data source select | | Users Select | ✅ Full | User picker with search and confirm dialog | | Conversations Select | ✅ Full | Conversation picker | | Channels Select | ✅ Full | Channel picker |
Multi-Select Menus
| Element | Status | Notes | | -------------------------- | ------- | ------------------------------- | | Multi Static Select | ✅ Full | Multi-select with option groups | | Multi External Select | ✅ Full | Multi-select with external data | | Multi Users Select | ✅ Full | Multi-user picker | | Multi Conversations Select | ✅ Full | Multi-conversation picker | | Multi Channels Select | ✅ Full | Multi-channel picker |
Input Elements
| Element | Status | Notes | | ---------------- | ------- | ---------------------------------- | | Plain Text Input | ✅ Full | Single/multiline with validation | | Email Input | ✅ Full | Email text input | | URL Input | ✅ Full | URL text input | | Number Input | ✅ Full | Numeric input with decimal support | | Date Picker | ✅ Full | Calendar date picker | | Time Picker | ✅ Full | Time picker (HH:mm) | | DateTime Picker | ✅ Full | Combined date and time picker | | File Input | ✅ Full | File upload element | | Rich Text Input | ✅ Full | Rich text editor |
Display Elements
| Element | Status | Notes |
| ---------- | ------- | ---------------------------------------------- |
| Image | ✅ Full | Simple image display with slack_file support |
| URL Source | ✅ Full | Clickable URL reference for task cards |
Rich Text Support
The library fully supports Slack's rich text block with all formatting options.
Text Styles
| Style | Slack Syntax | Result |
| ------------- | ------------ | ---------------- |
| Bold | *text* | text |
| Italic | _text_ | text |
| Strikethrough | ~text~ | ~~text~~ |
| Inline code | `code` | code |
| Highlight | - | Highlighted text |
Block Elements
- Lists - Ordered (numeric, alpha, roman) and unordered with up to 8 levels of indentation, with
offsetsupport for starting at a specific number - Quotes - Blockquote styling with left border
- Preformatted - Code blocks with monospace font and optional
languagesyntax highlighting - Color - Inline color swatch with hex value display
Mentions & Special Syntax
| Type | Syntax | Description |
| ------------------- | ------------------------------------------- | ---------------------------- |
| User mention | <@U123456> | Mentions a user |
| Channel mention | <#C123456> | Links to a channel |
| Usergroup mention | <!subteam^S123456> | Mentions a user group |
| Broadcast @here | <!here> | Notifies active users |
| Broadcast @channel | <!channel> | Notifies all channel members |
| Broadcast @everyone | <!everyone> | Notifies everyone |
| Link | <https://example.com\|Link Text> | Hyperlink with custom text |
| Date | <!date^1234567890^{date_short}\|fallback> | Formatted date |
Dark Mode
The library has built-in dark mode support. Use the theme prop on the Message component:
<Message blocks={blocks} name="Bot" logo="/logo.png" theme="dark" />If theme is not set, it automatically follows the system preference via prefers-color-scheme.
All components automatically adapt their colors, borders, and backgrounds for dark mode.
Hooks API
Hooks allow you to customize how specific elements are rendered.
Available Hooks
| Hook | Parameters | Description |
| ------------ | ---------------------------------------------- | ------------------------------------ |
| user | { id, name, style } | Custom user mention rendering |
| channel | { id, name, style } | Custom channel mention rendering |
| usergroup | { id, name, style } | Custom usergroup mention rendering |
| atHere | style | Custom @here broadcast rendering |
| atChannel | style | Custom @channel broadcast rendering |
| atEveryone | style | Custom @everyone broadcast rendering |
| emoji | data, defaultParser | Custom emoji rendering |
| date | { timestamp, format, link, fallback } | Custom date rendering |
| link | { href, children, className, target?, rel? } | Custom link/anchor rendering |
Examples
User Mention Hook
<Message
blocks={blocks}
hooks={{
user: ({ id, name, style }) => (
<span className="user-mention" style={{ fontWeight: style?.bold ? "bold" : "normal" }}>
@{name || id}
</span>
),
}}
/>Channel Mention Hook
<Message
blocks={blocks}
hooks={{
channel: ({ id, name }) => <a href={`/channels/${id}`}>#{name || id}</a>,
}}
/>Emoji Hook
<Message
blocks={blocks}
hooks={{
emoji: (data, defaultParser) => {
// Use custom emoji images or fall back to default
if (data.name === "custom_emoji") {
return <img src="/emojis/custom.png" alt={data.name} />;
}
return defaultParser(data);
},
}}
/>Date Hook
<Message
blocks={blocks}
hooks={{
date: ({ timestamp, format, link, fallback }) => {
const date = new Date(parseInt(timestamp) * 1000);
return <time dateTime={date.toISOString()}>{fallback}</time>;
},
}}
/>Broadcast Hooks
<Message
blocks={blocks}
hooks={{
atHere: (style) => <span className="broadcast">@here</span>,
atChannel: (style) => <span className="broadcast">@channel</span>,
atEveryone: (style) => <span className="broadcast">@everyone</span>,
}}
/>Link Hook
<Message
blocks={blocks}
hooks={{
link: ({ href, children, className, target, rel }) => (
<a href={href} target={target} rel={rel} className={className}>
{children}
</a>
),
}}
/>Automatic Mention Replacement
Pass user, channel, and usergroup data to automatically resolve mentions:
<Message
blocks={blocks}
data={{
users: [
{ id: "U123456", name: "John Doe", avatar: "https://..." },
{ id: "U789012", name: "Jane Smith", avatar: "https://..." },
],
channels: [
{ id: "C123456", name: "general" },
{ id: "C789012", name: "random" },
],
user_groups: [{ id: "S123456", name: "Engineering Team", handle: "engineering" }],
}}
/>When a mention like <@U123456> appears in blocks, it will automatically display "John Doe" instead of the raw ID.
Message Component Props
| Prop | Type | Default | Description |
| ------------------- | ------------------- | -------- | ------------------------------------------------------------------------------ |
| blocks | Block[] | required | Array of Slack block objects |
| name | string | required | Name of the sender/app |
| logo | string | required | URL of the logo to display |
| time | Date | - | Timestamp for the message |
| theme | "light" \| "dark" | system | Theme mode. Falls back to system preference if not set |
| className | string | - | Additional CSS classes |
| style | CSSProperties | - | Inline styles |
| unstyled | boolean | false | Disable all included styles |
| withoutWrapper | boolean | false | Render blocks without wrapper |
| hooks | Hooks | - | Custom rendering hooks |
| data | Data | - | Users, channels, usergroups data |
| showBlockKitDebug | boolean | false | Show Block Kit Builder link (custom properties are sanitized from the payload) |
TypeScript Support
The library exports all types for full type safety:
import type {
// Main types
Block,
// Block types
SectionBlock,
DividerBlock,
ImageBlock,
ContextBlock,
ActionsBlock,
HeaderBlock,
InputBlock,
RichTextBlock,
VideoBlock,
FileBlock,
ContextActionsBlock,
MarkdownBlock,
PlanBlock,
TaskCardBlock,
// Element types
ButtonElement,
ImageElement,
StaticSelectElement,
ExternalSelectElement,
UsersSelectElement,
ConversationsSelectElement,
ChannelsSelectElement,
MultiStaticSelectElement,
MultiExternalSelectElement,
MultiUsersSelectElement,
MultiConversationsSelectElement,
MultiChannelsSelectElement,
PlainTextInputElement,
EmailInputElement,
UrlTextInputElement,
NumberInputElement,
CheckboxesElement,
RadioButtonsElement,
DatePickerElement,
TimePickerElement,
DateTimePickerElement,
OverflowMenuElement,
FileInputElement,
RichTextInputElement,
WorkflowButtonElement,
FeedbackButtonsElement,
IconButtonElement,
UrlSourceElement,
// Rich text types
RichTextSection,
RichTextList,
RichTextQuote,
RichTextPreformatted,
RichTextSectionColor,
// Composition objects
TextObject,
ConfirmDialogObject,
OptionObject,
SlackFileObject,
WorkflowObject,
TriggerObject,
} from "slack-blocks-to-jsx";Theming & Customization
CSS Class Structure
The library uses a consistent BEM-like naming convention:
.slack_blocks_to_jsx /* Main wrapper */
├── .slack_blocks_to_jsx--header /* Header section */
├── .slack_blocks_to_jsx--blocks /* Blocks container */
│ └── .slack_blocks_to_jsx--block_wrapper /* Each block wrapper */
│ └── .slack_blocks_to_jsx__[block] /* Block-specific class */Block Classes
| Class | Description |
| --------------------------------------- | --------------------- |
| .slack_blocks_to_jsx__divider | Divider block |
| .slack_blocks_to_jsx__section | Section block |
| .slack_blocks_to_jsx__image | Image block |
| .slack_blocks_to_jsx__context | Context block |
| .slack_blocks_to_jsx__actions | Actions block |
| .slack_blocks_to_jsx__input | Input block |
| .slack_blocks_to_jsx__header | Header block |
| .slack_blocks_to_jsx__rich_text | Rich text block |
| .slack_blocks_to_jsx__video | Video block |
| .slack_blocks_to_jsx__table | Table block |
| .slack_blocks_to_jsx__context_actions | Context actions block |
| .slack_blocks_to_jsx__markdown_block | Markdown block |
| .slack_blocks_to_jsx__plan | Plan block |
| .slack_blocks_to_jsx__task_card | Task card block |
Element Classes
| Class | Description |
| ---------------------------------------------------------- | ----------------------- |
| .slack_blocks_to_jsx__button_element | Button element |
| .slack_blocks_to_jsx__workflow_button_element | Workflow button element |
| .slack_blocks_to_jsx__overflow_menu_element | Overflow menu |
| .slack_blocks_to_jsx__radio_buttons_element | Radio button group |
| .slack_blocks_to_jsx__checkboxes_element | Checkboxes group |
| .slack_blocks_to_jsx__date_picker_element | Date picker |
| .slack_blocks_to_jsx__time_picker_element | Time picker |
| .slack_blocks_to_jsx__datetime_picker_element | DateTime picker |
| .slack_blocks_to_jsx__plain_text_input_element | Plain text input |
| .slack_blocks_to_jsx__email_input_element | Email input |
| .slack_blocks_to_jsx__url_input_element | URL input |
| .slack_blocks_to_jsx__number_input_element | Number input |
| .slack_blocks_to_jsx__file_input_element | File input |
| .slack_blocks_to_jsx__rich_text_input_element | Rich text input |
| .slack_blocks_to_jsx__image_element | Image element |
| .slack_blocks_to_jsx__users_select_element | Users select |
| .slack_blocks_to_jsx__channels_select_element | Channels select |
| .slack_blocks_to_jsx__conversations_select_element | Conversations select |
| .slack_blocks_to_jsx__external_select_element | External select |
| .slack_blocks_to_jsx__multi_static_select_element | Multi static select |
| .slack_blocks_to_jsx__multi_external_select_element | Multi external select |
| .slack_blocks_to_jsx__multi_users_select_element | Multi users select |
| .slack_blocks_to_jsx__multi_conversations_select_element | Multi conversations |
| .slack_blocks_to_jsx__multi_channels_select_element | Multi channels select |
| .slack_blocks_to_jsx__feedback_buttons_element | Feedback buttons |
| .slack_blocks_to_jsx__icon_button_element | Icon button |
| .slack_blocks_to_jsx__url_source_element | URL source |
| .slack_blocks_to_jsx__confirm_dialog | Confirmation dialog |
Rich Text Element Classes
| Class | Description |
| ------------------------------------------------------------ | -------------------- |
| .slack_blocks_to_jsx__rich_text_section_element | Section element |
| .slack_blocks_to_jsx__rich_text_list_element | List element |
| .slack_blocks_to_jsx__rich_text_quote_element | Quote element |
| .slack_blocks_to_jsx__rich_text_preformatted_element | Preformatted element |
| .slack_blocks_to_jsx__rich_text_section_element_text | Text element |
| .slack_blocks_to_jsx__rich_text_section_element_user | User mention |
| .slack_blocks_to_jsx__rich_text_section_element_channel | Channel mention |
| .slack_blocks_to_jsx__rich_text_section_element_user_group | Usergroup mention |
| .slack_blocks_to_jsx__rich_text_section_element_broadcast | Broadcast mention |
| .slack_blocks_to_jsx__rich_text_section_element_link | Link element |
| .slack_blocks_to_jsx__rich_text_section_element_emoji | Emoji element |
| .slack_blocks_to_jsx__rich_text_section_element_date | Date element |
| .slack_blocks_to_jsx__rich_text_section_element_color | Color swatch element |
Override Examples
/* Custom link color */
.slack_blocks_to_jsx a {
color: #1264a3;
}
/* Custom code block styling */
.slack_blocks_to_jsx__rich_text_preformatted_element {
background: #1e1e1e;
color: #d4d4d4;
}
/* Custom mention styling */
.slack_blocks_to_jsx__rich_text_section_element_user {
background: #e8f5fa;
padding: 0 4px;
border-radius: 3px;
}
/* Custom quote styling */
.slack_blocks_to_jsx__rich_text_quote_element {
border-left: 4px solid #1264a3;
padding-left: 12px;
}Complete Example
import { Message, Block } from "slack-blocks-to-jsx";
import "slack-blocks-to-jsx/dist/style.css";
const blocks: Block[] = [
{
type: "header",
text: { type: "plain_text", text: "Welcome to the Team!" },
},
{
type: "section",
text: {
type: "mrkdwn",
text: "Hey <@U123>! Welcome to <#C456|general> :tada:",
},
},
{
type: "divider",
},
{
type: "section",
text: {
type: "mrkdwn",
text: "*Getting Started:*\n• Read the onboarding docs\n• Set up your development environment\n• Join the standup tomorrow",
},
},
{
type: "context",
elements: [{ type: "mrkdwn", text: "Posted by *HR Bot* :robot_face:" }],
},
];
function App() {
return (
<Message
blocks={blocks}
name="HR Bot"
logo="/bot-logo.png"
time={new Date()}
data={{
users: [{ id: "U123", name: "New Employee" }],
channels: [{ id: "C456", name: "general" }],
}}
hooks={{
user: ({ name }) => <strong className="text-blue-600">@{name}</strong>,
}}
/>
);
}
export default App;FAQ
How do I render messages without the header?
Use the withoutWrapper prop:
<Message blocks={blocks} withoutWrapper />How do I disable default styles?
Use the unstyled prop and provide your own CSS:
<Message blocks={blocks} unstyled />How do I debug my blocks?
Enable the Block Kit Builder link:
<Message blocks={blocks} showBlockKitDebug />This adds a link to open your blocks in Slack's Block Kit Builder. Custom properties (like people on users_select, iframeProps on video, etc.) are automatically stripped from the payload so it passes Slack's validation.
Why isn't my mention showing the user's name?
Make sure you're either:
- Passing user data via the
dataprop, or - Handling it via the
userhook
Does this work with Next.js / SSR?
Yes! See the Next.js / SSR Setup section above for the required config.
Can I use this with Tailwind CSS?
Yes, the library uses Tailwind internally. You can extend or override styles using Tailwind classes or the provided CSS class names.
What React versions are supported?
React 17, 18, and 19 are all supported.
How do I handle interactive elements like buttons?
Interactive elements (buttons, selects, checkboxes, etc.) render visually with built-in UI interactions like dropdowns, selection, and confirm dialogs. For custom business logic, use the component's structure to add your own event handling or wrap the Message component.
How does the Markdown block differ from Rich Text?
The Rich Text block uses Slack's mrkdwn syntax (parsed internally). The Markdown block uses standard markdown (GitHub Flavored Markdown) and is rendered via react-markdown — it supports tables, task lists, code blocks with syntax highlighting, and more. The Markdown block is designed for AI/LLM output in Slack.
Contributing
The project is open-source and contributions are welcome! Here's how you can help:
- Report bugs - Open an issue
- Request features - Describe your use case in an issue
- Submit PRs - Fork the repo and submit a pull request
- Improve docs - Help make the documentation better
For feature requests or custom implementations, you can also reach out at [email protected].
Support
Love this library? Consider supporting its development:
License
MIT License - see LICENSE for details.
