Build Slack Block Kit UIs for messages, modals, and Home tabs. Use when creating Slack notifications, interactive forms, bot responses, app dashboards, or any Slack UI. Covers blocks (Section, Actions, Input, Header), elements (Buttons, Selects, Date pickers), composition objects, and the slack-block-builder library.
Builds Slack Block Kit UIs for messages, modals, and Home tabs using JSON or slack-block-builder.
npx claudepluginhub linehaul-ai/linehaulai-claude-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Expert guide for building Slack UIs with Block Kit.
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:
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 |
{
"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"
}
]
}
]
}
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();
};
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 charsSurfaces: Messages, Modals, Home tabs
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 defaultSurfaces: Messages, Modals, Home tabs
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 identifierSurfaces: Messages, Modals, Home tabs
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 changeSurfaces: Modals, Messages, Home tabs
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 identifierSurfaces: Messages, Modals, Home tabs
Visual separator between blocks.
{
"type": "divider"
}
Surfaces: Messages, Modals, Home tabs
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
Displays formatted markdown (messages only).
{
"type": "markdown",
"text": "**Important:** This is *formatted* text with `code`."
}
Surfaces: Messages only
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
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
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 charsWorks with: Section (accessory), Actions
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
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 textSelect workspace users.
{
"type": "users_select",
"action_id": "assignee_select",
"placeholder": {
"type": "plain_text",
"text": "Select assignee"
},
"initial_user": "U123ABC"
}
Select public channels.
{
"type": "channels_select",
"action_id": "channel_select",
"placeholder": {
"type": "plain_text",
"text": "Select channel"
},
"initial_channel": "C123ABC"
}
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
}
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
}
Select a date.
{
"type": "datepicker",
"action_id": "due_date",
"placeholder": {
"type": "plain_text",
"text": "Select due date"
},
"initial_date": "2025-12-31"
}
Select a time.
{
"type": "timepicker",
"action_id": "meeting_time",
"placeholder": {
"type": "plain_text",
"text": "Select time"
},
"initial_time": "14:30"
}
Select date and time together.
{
"type": "datetimepicker",
"action_id": "event_datetime",
"initial_date_time": 1734307200
}
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"
}
]
}
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"
}
}
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
}
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 with validation.
{
"type": "url_text_input",
"action_id": "website_input",
"placeholder": {
"type": "plain_text",
"text": "https://example.com"
}
}
Email input with validation.
{
"type": "email_text_input",
"action_id": "email_input",
"placeholder": {
"type": "plain_text",
"text": "you@example.com"
}
}
Formatted text input.
{
"type": "rich_text_input",
"action_id": "content_input",
"placeholder": {
"type": "plain_text",
"text": "Write your content..."
}
}
File upload (modals only).
{
"type": "file_input",
"action_id": "attachment_input",
"filetypes": ["pdf", "png", "jpg"],
"max_files": 3
}
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"
}
]
}
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 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"
}
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"
}
Grouped options for select menus.
{
"label": {
"type": "plain_text",
"text": "Group Label"
},
"options": [
{
"text": {
"type": "plain_text",
"text": "Option 1"
},
"value": "option_1"
}
]
}
{
"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
}
}
]
}
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 initiallynpm install slack-block-builder
import {
Message,
Modal,
HomeTab,
Blocks,
Elements,
Bits,
Md
} from 'slack-block-builder';
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();
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();
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
// Returns JavaScript object
modal.buildToObject();
// Returns JSON string
modal.buildToJSON();
// Returns only blocks array
modal.getBlocks();
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_submitalt_text for imagesaccessibility_label for buttons with iconsMessage({ 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();
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();
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();
| 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 |
For comprehensive guides, see the references/ directory:
Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.