Slack Block Kit
Expert guide for building Slack UIs with Block Kit.
Overview
Block Kit is Slack's UI framework for creating rich, interactive messages, modals, and Home tabs. It uses a JSON-based structure with three main components:
- Blocks: Layout containers (Section, Actions, Input, Header, etc.)
- Elements: Interactive components (Buttons, Selects, Date pickers)
- Composition Objects: Text, options, and confirmations
Surfaces
Block Kit works on three surfaces:
| Surface | Max Blocks | Use Cases | |---------|------------|-----------| | Messages | 50 | Notifications, bot responses, channel posts | | Modals | 100 | Forms, confirmations, multi-step workflows | | Home tabs | 100 | App dashboards, user-specific content |
Quick Start
Simple Message
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "New Order Received"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Order ID:*\n#12345"
},
{
"type": "mrkdwn",
"text": "*Customer:*\nJohn Doe"
}
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Order"
},
"style": "primary",
"action_id": "view_order",
"value": "12345"
}
]
}
]
}
Using slack-block-builder (JavaScript)
import { Message, Blocks, Elements } from 'slack-block-builder';
const orderNotification = ({ orderId, customer, channel }) => {
return Message({ channel, text: 'New Order Received' })
.blocks(
Blocks.Header({ text: 'New Order Received' }),
Blocks.Section()
.fields([
`*Order ID:*\n#${orderId}`,
`*Customer:*\n${customer}`
]),
Blocks.Actions()
.elements(
Elements.Button({ text: 'View Order', actionId: 'view_order' })
.primary()
.value(orderId)
)
)
.buildToObject();
};
Blocks Reference
Header Block
Displays large text for titles.
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Budget Performance",
"emoji": true
}
}
Fields:
type(required): Always"header"text(required): plain_text object, max 150 charsblock_id(optional): Unique identifier, max 255 chars
Surfaces: Messages, Modals, Home tabs
Section Block
Most versatile block for text and accessories.
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Project Update*\nThe deployment is complete."
},
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"text": "View Details"
},
"action_id": "view_details"
}
}
With Fields (two-column layout):
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Status:*\nApproved"
},
{
"type": "mrkdwn",
"text": "*Created:*\nDec 15, 2025"
},
{
"type": "mrkdwn",
"text": "*Priority:*\nHigh"
},
{
"type": "mrkdwn",
"text": "*Assignee:*\n<@U123ABC>"
}
]
}
Fields:
type(required): Always"section"text(optional): Text object (mrkdwn or plain_text), max 3000 charsfields(optional): Array of text objects (max 10), each max 2000 charsaccessory(optional): One interactive elementblock_id(optional): Unique identifierexpand(optional): Boolean, expand text by default
Surfaces: Messages, Modals, Home tabs
Actions Block
Contains multiple interactive elements.
{
"type": "actions",
"block_id": "approval_actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Approve"
},
"style": "primary",
"action_id": "approve_request",
"value": "approved"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Reject"
},
"style": "danger",
"action_id": "reject_request",
"value": "rejected"
}
]
}
Fields:
type(required): Always"actions"elements(required): Array of interactive elements (max 25)block_id(optional): Unique identifier
Surfaces: Messages, Modals, Home tabs
Input Block
Collects user input (modals and Home tabs primarily).
{
"type": "input",
"block_id": "description_input",
"label": {
"type": "plain_text",
"text": "Description"
},
"element": {
"type": "plain_text_input",
"action_id": "description_value",
"multiline": true,
"placeholder": {
"type": "plain_text",
"text": "Enter a description..."
}
},
"optional": true,
"hint": {
"type": "plain_text",
"text": "Provide additional context"
}
}
Fields:
type(required): Always"input"element(required): Input element (text input, select, date picker, etc.)label(required): plain_text object, max 2000 charsblock_id(optional): Unique identifierhint(optional): plain_text object, max 2000 charsoptional(optional): Boolean, default falsedispatch_action(optional): Boolean, dispatch block_actions on change
Surfaces: Modals, Messages, Home tabs
Context Block
Displays secondary, contextual information.
{
"type": "context",
"elements": [
{
"type": "image",
"image_url": "https://example.com/avatar.png",
"alt_text": "User avatar"
},
{
"type": "mrkdwn",
"text": "Posted by <@U123ABC> on Dec 15, 2025"
}
]
}
Fields:
type(required): Always"context"elements(required): Array of image/text elements (max 10)block_id(optional): Unique identifier
Surfaces: Messages, Modals, Home tabs
Divider Block
Visual separator between blocks.
{
"type": "divider"
}
Surfaces: Messages, Modals, Home tabs
Image Block
Displays an image.
{
"type": "image",
"image_url": "https://example.com/chart.png",
"alt_text": "Sales chart",
"title": {
"type": "plain_text",
"text": "Q4 Sales Performance"
}
}
Fields:
type(required): Always"image"image_url(required*): Public URL, max 3000 charsslack_file(required*): Alternative to image_urlalt_text(required): Description, max 2000 charstitle(optional): plain_text object, max 2000 charsblock_id(optional): Unique identifier
*One of image_url or slack_file is required
Surfaces: Messages, Modals, Home tabs
Markdown Block
Displays formatted markdown (messages only).
{
"type": "markdown",
"text": "**Important:** This is *formatted* text with `code`."
}
Surfaces: Messages only
Rich Text Block
Structured text representation.
{
"type": "rich_text",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Hello ",
"style": {
"bold": true
}
},
{
"type": "user",
"user_id": "U123ABC"
}
]
}
]
}
Surfaces: Messages, Modals, Home tabs
Video Block
Embeds a video player.
{
"type": "video",
"title": {
"type": "plain_text",
"text": "Product Demo"
},
"video_url": "https://example.com/video.mp4",
"thumbnail_url": "https://example.com/thumb.png",
"alt_text": "Product demonstration video"
}
Surfaces: Messages, Modals, Home tabs
Elements Reference
Button Element
Interactive button for actions.
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Click Me",
"emoji": true
},
"action_id": "button_click",
"value": "button_value",
"style": "primary"
}
Fields:
type(required): Always"button"text(required): plain_text object, max 75 charsaction_id(optional): Identifier for interaction, max 255 charsvalue(optional): Payload value, max 2000 charsstyle(optional):"primary"(green) or"danger"(red)url(optional): URL to open, max 3000 charsconfirm(optional): Confirmation dialogaccessibility_label(optional): Screen reader text, max 75 chars
Works with: Section (accessory), Actions
Static Select Menu
Dropdown with predefined options.
{
"type": "static_select",
"action_id": "priority_select",
"placeholder": {
"type": "plain_text",
"text": "Select priority"
},
"options": [
{
"text": {
"type": "plain_text",
"text": "High"
},
"value": "high"
},
{
"text": {
"type": "plain_text",
"text": "Medium"
},
"value": "medium"
},
{
"text": {
"type": "plain_text",
"text": "Low"
},
"value": "low"
}
],
"initial_option": {
"text": {
"type": "plain_text",
"text": "Medium"
},
"value": "medium"
}
}
Fields:
type(required):"static_select"action_id(optional): Interaction identifieroptions(required*): Array of option objects (max 100)option_groups(required*): Grouped options (max 100 groups)initial_option(optional): Pre-selected optionplaceholder(optional): Placeholder text, max 150 charsconfirm(optional): Confirmation dialogfocus_on_load(optional): Auto-focus in modals
*One of options or option_groups required
External Select Menu
Dropdown with dynamically loaded options.
{
"type": "external_select",
"action_id": "customer_select",
"placeholder": {
"type": "plain_text",
"text": "Search customers..."
},
"min_query_length": 2
}
Fields:
type(required):"external_select"action_id(optional): Interaction identifiermin_query_length(optional): Min chars before query (default 3)initial_option(optional): Pre-selected optionplaceholder(optional): Placeholder text
Users Select Menu
Select workspace users.
{
"type": "users_select",
"action_id": "assignee_select",
"placeholder": {
"type": "plain_text",
"text": "Select assignee"
},
"initial_user": "U123ABC"
}
Channels Select Menu
Select public channels.
{
"type": "channels_select",
"action_id": "channel_select",
"placeholder": {
"type": "plain_text",
"text": "Select channel"
},
"initial_channel": "C123ABC"
}
Conversations Select Menu
Select any conversation (channels, DMs, groups).
{
"type": "conversations_select",
"action_id": "conversation_select",
"placeholder": {
"type": "plain_text",
"text": "Select conversation"
},
"default_to_current_conversation": true
}
Multi-Select Menus
All select menus have multi-select variants:
multi_static_selectmulti_external_selectmulti_users_selectmulti_channels_selectmulti_conversations_select
{
"type": "multi_users_select",
"action_id": "team_select",
"placeholder": {
"type": "plain_text",
"text": "Select team members"
},
"max_selected_items": 5
}
Date Picker
Select a date.
{
"type": "datepicker",
"action_id": "due_date",
"placeholder": {
"type": "plain_text",
"text": "Select due date"
},
"initial_date": "2025-12-31"
}
Time Picker
Select a time.
{
"type": "timepicker",
"action_id": "meeting_time",
"placeholder": {
"type": "plain_text",
"text": "Select time"
},
"initial_time": "14:30"
}
Datetime Picker
Select date and time together.
{
"type": "datetimepicker",
"action_id": "event_datetime",
"initial_date_time": 1734307200
}
Checkboxes
Multiple selection from options.
{
"type": "checkboxes",
"action_id": "features_select",
"options": [
{
"text": {
"type": "plain_text",
"text": "Email notifications"
},
"value": "email",
"description": {
"type": "mrkdwn",
"text": "*Receive daily summary*"
}
},
{
"text": {
"type": "plain_text",
"text": "Slack notifications"
},
"value": "slack"
}
],
"initial_options": [
{
"text": {
"type": "plain_text",
"text": "Email notifications"
},
"value": "email"
}
]
}
Radio Buttons
Single selection from options.
{
"type": "radio_buttons",
"action_id": "urgency_select",
"options": [
{
"text": {
"type": "plain_text",
"text": "Urgent"
},
"value": "urgent"
},
{
"text": {
"type": "plain_text",
"text": "Standard"
},
"value": "standard"
}
],
"initial_option": {
"text": {
"type": "plain_text",
"text": "Standard"
},
"value": "standard"
}
}
Plain Text Input
Single or multi-line text input.
{
"type": "plain_text_input",
"action_id": "comment_input",
"placeholder": {
"type": "plain_text",
"text": "Enter your comment..."
},
"multiline": true,
"min_length": 10,
"max_length": 500
}
Number Input
Numeric input with validation.
{
"type": "number_input",
"action_id": "quantity_input",
"is_decimal_allowed": false,
"min_value": "1",
"max_value": "100",
"placeholder": {
"type": "plain_text",
"text": "Enter quantity"
}
}
URL Input
URL input with validation.
{
"type": "url_text_input",
"action_id": "website_input",
"placeholder": {
"type": "plain_text",
"text": "https://example.com"
}
}
Email Input
Email input with validation.
{
"type": "email_text_input",
"action_id": "email_input",
"placeholder": {
"type": "plain_text",
"text": "you@example.com"
}
}
Rich Text Input
Formatted text input.
{
"type": "rich_text_input",
"action_id": "content_input",
"placeholder": {
"type": "plain_text",
"text": "Write your content..."
}
}
File Input
File upload (modals only).
{
"type": "file_input",
"action_id": "attachment_input",
"filetypes": ["pdf", "png", "jpg"],
"max_files": 3
}
Overflow Menu
Compact menu for secondary actions.
{
"type": "overflow",
"action_id": "more_actions",
"options": [
{
"text": {
"type": "plain_text",
"text": "Edit"
},
"value": "edit"
},
{
"text": {
"type": "plain_text",
"text": "Delete"
},
"value": "delete"
}
]
}
Composition Objects
Text Object
Used throughout Block Kit for text content.
Plain Text:
{
"type": "plain_text",
"text": "Hello, world!",
"emoji": true
}
Markdown (mrkdwn):
{
"type": "mrkdwn",
"text": "*Bold* _italic_ ~strike~ `code` <https://example.com|link>"
}
Confirmation Dialog
Confirmation before destructive actions.
{
"title": {
"type": "plain_text",
"text": "Confirm Delete"
},
"text": {
"type": "mrkdwn",
"text": "Are you sure you want to delete this item? This cannot be undone."
},
"confirm": {
"type": "plain_text",
"text": "Delete"
},
"deny": {
"type": "plain_text",
"text": "Cancel"
},
"style": "danger"
}
Option Object
For select menus, checkboxes, and radio buttons.
{
"text": {
"type": "plain_text",
"text": "Option Label"
},
"value": "option_value",
"description": {
"type": "plain_text",
"text": "Optional description"
},
"url": "https://example.com"
}
Option Group
Grouped options for select menus.
{
"label": {
"type": "plain_text",
"text": "Group Label"
},
"options": [
{
"text": {
"type": "plain_text",
"text": "Option 1"
},
"value": "option_1"
}
]
}
Modal Reference
Opening a Modal
{
"type": "modal",
"callback_id": "feedback_modal",
"title": {
"type": "plain_text",
"text": "Submit Feedback"
},
"submit": {
"type": "plain_text",
"text": "Submit"
},
"close": {
"type": "plain_text",
"text": "Cancel"
},
"blocks": [
{
"type": "input",
"block_id": "feedback_type",
"label": {
"type": "plain_text",
"text": "Feedback Type"
},
"element": {
"type": "static_select",
"action_id": "type_select",
"options": [
{
"text": {
"type": "plain_text",
"text": "Bug Report"
},
"value": "bug"
},
{
"text": {
"type": "plain_text",
"text": "Feature Request"
},
"value": "feature"
}
]
}
},
{
"type": "input",
"block_id": "feedback_content",
"label": {
"type": "plain_text",
"text": "Description"
},
"element": {
"type": "plain_text_input",
"action_id": "content_input",
"multiline": true
}
}
]
}
Modal Fields
type(required): Always"modal"title(required): plain_text, max 24 charsblocks(required): Array of blocks (max 100)callback_id(optional): Identifier for view_submissionsubmit(optional): Submit button text, max 24 charsclose(optional): Close button text, max 24 charsprivate_metadata(optional): String passed to submission, max 3000 charsclear_on_close(optional): Clear all views on closenotify_on_close(optional): Send view_closed eventexternal_id(optional): Custom identifiersubmit_disabled(optional): Disable submit button initially
slack-block-builder Library
Installation
npm install slack-block-builder
Imports
import {
Message,
Modal,
HomeTab,
Blocks,
Elements,
Bits,
Md
} from 'slack-block-builder';
Building Messages
const message = Message({ channel: 'C123ABC', text: 'Fallback text' })
.blocks(
Blocks.Header({ text: 'Welcome!' }),
Blocks.Section({ text: 'Hello, *world*!' }),
Blocks.Divider(),
Blocks.Actions()
.elements(
Elements.Button({ text: 'Click Me', actionId: 'click' })
.primary()
)
)
.buildToObject();
Building Modals
const modal = Modal({ title: 'My Form', callbackId: 'form_submit' })
.submit('Save')
.close('Cancel')
.blocks(
Blocks.Input({ label: 'Name', blockId: 'name_block' })
.element(
Elements.TextInput({ actionId: 'name_input' })
.placeholder('Enter your name')
),
Blocks.Input({ label: 'Priority', blockId: 'priority_block' })
.element(
Elements.StaticSelect({ actionId: 'priority_select' })
.options(
Bits.Option({ text: 'High', value: 'high' }),
Bits.Option({ text: 'Medium', value: 'medium' }),
Bits.Option({ text: 'Low', value: 'low' })
)
)
)
.buildToObject();
Markdown Helpers
import { Md } from 'slack-block-builder';
Md.bold('text') // *text*
Md.italic('text') // _text_
Md.strike('text') // ~text~
Md.code('text') // `text`
Md.codeBlock('text') // ```text```
Md.link('url', 'text') // <url|text>
Md.user('U123') // <@U123>
Md.channel('C123') // <#C123>
Md.emoji('smile') // :smile:
Md.listBullet(['a', 'b']) // • a\n• b
Build Methods
// Returns JavaScript object
modal.buildToObject();
// Returns JSON string
modal.buildToJSON();
// Returns only blocks array
modal.getBlocks();
Best Practices
Naming Conventions
action_id:{verb}_{noun}- e.g.,submit_form,select_priorityblock_id:{noun}_block- e.g.,name_block,options_blockcallback_id:{feature}_{action}- e.g.,feedback_submit
Performance
- Keep messages under 50 blocks
- Keep modals under 100 blocks
- Use external_select for large option lists (>100 items)
- Minimize image usage in high-traffic messages
Accessibility
- Always provide
alt_textfor images - Use
accessibility_labelfor buttons with icons - Ensure sufficient color contrast
- Provide clear, descriptive labels
Mobile Considerations
- Use shorter text (truncation occurs ~30 chars for buttons)
- Limit fields in Section blocks (2-3 max)
- Test in mobile Slack client
- Avoid complex nested layouts
Common Patterns
Notification with Actions
Message({ channel, text: 'New request' })
.blocks(
Blocks.Header({ text: 'New Request' }),
Blocks.Section()
.fields([
`*From:*\n${requester}`,
`*Priority:*\n${priority}`
]),
Blocks.Context()
.elements([`Submitted ${timestamp}`]),
Blocks.Divider(),
Blocks.Actions()
.elements(
Elements.Button({ text: 'Approve', actionId: 'approve' }).primary(),
Elements.Button({ text: 'Reject', actionId: 'reject' }).danger()
)
)
.buildToObject();
Form Modal with Validation
Modal({ title: 'Create Task', callbackId: 'task_create' })
.submit('Create')
.close('Cancel')
.blocks(
Blocks.Input({ label: 'Task Name', blockId: 'name' })
.element(Elements.TextInput({ actionId: 'name_input' })),
Blocks.Input({ label: 'Assignee', blockId: 'assignee' })
.element(Elements.UsersSelect({ actionId: 'assignee_select' })),
Blocks.Input({ label: 'Due Date', blockId: 'due_date' })
.element(Elements.DatePicker({ actionId: 'date_select' }))
.optional(true)
)
.buildToObject();
Home Tab Dashboard
HomeTab()
.blocks(
Blocks.Header({ text: `Welcome, ${userName}!` }),
Blocks.Section({ text: 'Here are your pending tasks:' }),
Blocks.Divider(),
...tasks.map(task =>
Blocks.Section({ text: `• ${task.title}` })
.accessory(
Elements.Button({ text: 'Complete', actionId: `complete_${task.id}` })
)
),
Blocks.Divider(),
Blocks.Actions()
.elements(
Elements.Button({ text: 'New Task', actionId: 'new_task' }).primary()
)
)
.buildToObject();
Troubleshooting
Common Errors
| Error | Cause | Solution |
|-------|-------|----------|
| invalid_blocks | Malformed JSON | Validate in Block Kit Builder |
| too_many_blocks | Exceeds limit | Reduce to 50 (msg) or 100 (modal) |
| invalid_element | Wrong element in block | Check element compatibility |
| missing_text | Required text missing | Add text or fields to Section |
Validation Tips
- Use Block Kit Builder to test JSON
- Check element compatibility with blocks
- Verify required fields are present
- Test on mobile for display issues
Resources
Reference Documentation
For comprehensive guides, see the references/ directory:
- blocks.md - Detailed documentation on all block types
- interactive-components.md - Complete guide to interactive elements
- layout-patterns.md - Composition and nesting best practices
- surfaces.md - Messages, modals, and home tabs deep dive