Rendering messages
<BlockKit /> takes one required prop, data, and renders the whole payload.
What follows is what data accepts, how surfaces work, and how to compose
individual blocks when you don't want the all-in-one component.
Accepted input shapes
| Shape | Example |
|---|---|
| Payload object | { blocks: [ … ] } |
| Block array | [ { type: "section", … } ] |
| JSON string | '{"blocks":[…]}' or '[…]' |
A JSON string is parsed before rendering; malformed JSON throws
BlockKitInvariantError with code: "invalid_json". An editor can pass its
current JSON directly when the host catches that error:
import { BlockKit } from "react-blockkit";
const jsonFromEditor = '{"blocks":[{"type":"section","text":{"type":"mrkdwn","text":"hi"}}]}';
export function Preview() {
return <BlockKit data={jsonFromEditor} />;
}Rendering is tolerant and does not call the strict validators. Unknown or future discriminators produce labeled fallbacks; malformed known shapes render the usable fields or a local fallback instead of rejecting the whole payload. See Unknown blocks and fallbacks for the degradation behavior, and the validation reference for strict package checks and known compatibility limitations.
A fuller message
Blocks render in payload order with Slack's spacing rhythm:
Deploy summary
v2.41.0main · 3 checks passedView payload JSON
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Deploy summary"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Service:*\napi-gateway"
},
{
"type": "mrkdwn",
"text": "*Version:*\n`v2.41.0`"
},
{
"type": "mrkdwn",
"text": "*Region:*\nus-east-1"
},
{
"type": "mrkdwn",
"text": "*Status:*\n:white_check_mark: Healthy"
}
]
},
{
"type": "divider"
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Triggered from `main` · 3 checks passed"
}
]
}
]
}Surfaces
Slack renders blocks in three surfaces. Pass the one you are previewing:
import { BlockKit, type BlockKitInput } from "react-blockkit";
export function Preview({ data }: { data: BlockKitInput }) {
return <BlockKit data={data} surface="modal" />;
}surface is "message" (default), "modal", or "home". It sets the root's
data-surface attribute and provider context, and it selects the presentation
Slack uses on that surface (measured in the Block Kit Builder):
message: small 28px controls,actionselements flowing inline, 4px between blocks.modal: medium 36px controls ininputblocks, small controls in a stretched two-columnactionsgrid, 12px between blocks (4px beforecontextanddivider), andimageblocks centred with the title beneath.home: medium 36px controls everywhere with inlineactions, andimageblocks with the title beneath.
On every surface an actions block shows its first five elements and folds
the rest behind a "+ N more" button. The prop does not validate or filter
blocks. This message example includes an input block, which both Slack's
current message surface and the renderer support:
Deploy summary
v2.41.0 is live in us-east-1.View payload JSON
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Deploy summary"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*api-gateway* `v2.41.0` is live in `us-east-1`."
}
},
{
"type": "input",
"label": {
"type": "plain_text",
"text": "Rollback note"
},
"element": {
"type": "plain_text_input",
"action_id": "note"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"action_id": "confirm",
"style": "primary",
"text": {
"type": "plain_text",
"text": "Confirm"
}
}
]
}
]
}Validation helpers have their own, separate surface argument:
import { assertBlockKitData } from "react-blockkit";
const modalOnly = {
blocks: [
{
type: "alert",
level: "info",
text: { type: "plain_text", text: "Heads up" },
},
],
} as const;
// Throws BlockKitInvariantError with code "surface_mismatch".
assertBlockKitData(modalOnly, "message");Pass the intended surface directly to
parseBlockKitData or assertBlockKitData
when you need the package's strict surface checks. The current validator
applies a modal/home-only rule to input blocks and therefore rejects them for
"message" even though Slack and the renderer support them there. The
validation reference documents that compatibility limitation.
Compose individual blocks
Every layout block and core element is exported as its own component. Wrap
composed blocks in BlockKitProvider so they share the same surface,
resolvers, and action handler that <BlockKit /> would provide:
import { BlockKitProvider, ButtonElement, SectionBlock } from "react-blockkit";
export function ComposedPreview() {
return (
<BlockKitProvider surface="message">
<SectionBlock
block={{
type: "section",
text: { type: "plain_text", text: "Composable block" },
}}
/>
<ButtonElement
element={{
type: "button",
action_id: "continue",
text: { type: "plain_text", text: "Continue" },
}}
/>
</BlockKitProvider>
);
}Standalone block components take the typed Slack object as a block prop;
element components take element. The dispatch components Block and
InteractiveElement provide the unknown-discriminator fallbacks used by
<BlockKit />. The full component and prop list is in the
<BlockKit /> reference.
theme lives on <BlockKit />, not on BlockKitProvider, so standalone
blocks use the stylesheet's default light variables. The global CSS import is
still required. See
Theming.