Handling actions
Buttons, selects, overflow menus, checkboxes, radio buttons, inputs, and the
context_actions elements all report user interaction through a single
onAction callback:
import { BlockKit, type BlockKitAction, type BlockKitInput } from "react-blockkit";
export function Preview({ data }: { data: BlockKitInput }) {
return (
<BlockKit
data={data}
onAction={(action: BlockKitAction) => {
console.log(action.actionId, action.value);
}}
/>
);
}BlockKitAction is react-blockkit's own local event shape, not the
interactivity payload Slack POSTs to an app. Nothing is sent to Slack.
onAction hands you a normalized preview interaction; your app decides
whether to update local state or call its own backend. The callback receives no
DOM event and cannot cancel a URL button's navigation.
react-blockkit calls onAction from a React event handler and does not catch
or translate callback failures. React and the browser host decide how an
uncaught event-handler error is reported; it does not become a
BlockKitInvariantError. Handle synchronous errors and rejected async work
inside your callback when the application needs controlled recovery.
The BlockKitAction shape
| Field | Type | Meaning |
|---|---|---|
actionId | string | The element's action_id |
type | string | The element's type ("button", "static_select", …) |
blockId | string | undefined | The enclosing block's block_id, when set |
value | string | undefined | Single value (button value, selected option, input text) |
values | readonly string[] | undefined | Multiple values (multi-selects, checked checkboxes) |
Elements without an action_id never emit, so the callback fires only when
there is an ID to report. Optional fields are omitted from the action object
rather than set to undefined.
What each element emits
| Element | Fires on | Payload |
|---|---|---|
| Button | Click | value when the button declares one; URL buttons render as links and still emit |
| Overflow menu | Click | actionId and type only; the ellipsis button opens no menu, so no option is reported |
| Select (all variants) | Selection change | value, or values for multi_* variants |
| Checkboxes | Toggle | values: every currently checked option |
| Radio buttons | Selection | value of the chosen option |
| Text/email/URL/number inputs | Each change | value: the current text |
| Rich text input | Each change | value: the editor's plain text |
| File input | Selection | value: the first selected file's name |
| Date picker | Click | value: the initial_date, or an empty string (renders as a button) |
| Time and datetime pickers | Each change | value: the control's current value |
| Icon button | Click | value when the element declares one |
| Feedback buttons | Click | value from the pressed nested button's value; accessible name from its text.text |
These controls are preview approximations. Overflow does not open a menu,
external and workspace-backed selects do not fetch options, datepicker emits
its initial value on click, and workflow_button is display-only. The
renderer also does not reproduce Slack focus management or keyboard
shortcuts. See Accessibility when
the preview itself is user-facing.
For feedback controls, the positive button is named from
positive_button.text.text and the negative button from
negative_button.text.text. The parent does not supply those two labels.
Try it
Interact with the message below; the local BlockKitAction each element
emits appears under the render.
View payload JSON
{
"blocks": [
{
"type": "section",
"block_id": "review",
"text": {
"type": "mrkdwn",
"text": "*PR #482* is ready for review."
},
"accessory": {
"type": "static_select",
"action_id": "assign_reviewer",
"placeholder": {
"type": "plain_text",
"text": "Assign reviewer"
},
"options": [
{
"text": {
"type": "plain_text",
"text": "Maya"
},
"value": "maya"
},
{
"text": {
"type": "plain_text",
"text": "Sam"
},
"value": "sam"
}
]
}
},
{
"type": "actions",
"block_id": "decision",
"elements": [
{
"type": "button",
"action_id": "approve",
"text": {
"type": "plain_text",
"text": "Approve"
},
"style": "primary",
"value": "pr-482"
},
{
"type": "button",
"action_id": "request_changes",
"text": {
"type": "plain_text",
"text": "Request changes"
}
}
]
}
]
}Composed blocks
When rendering blocks individually, put onAction on BlockKitProvider, the
same context <BlockKit /> uses internally:
import {
ActionsBlock,
BlockKitProvider,
type ActionsBlockData,
type BlockKitAction,
} from "react-blockkit";
export function record(action: BlockKitAction) {
console.log(action);
}
export function Preview({ actionsBlock }: { actionsBlock: ActionsBlockData }) {
return (
<BlockKitProvider onAction={record}>
<ActionsBlock block={actionsBlock} />
</BlockKitProvider>
);
}