Theming
The renderer ships two themes, styled to match Slack's light and dark
appearances. Switch with the theme prop:
import { BlockKit, type BlockKitInput } from "react-blockkit";
export function Preview({ data }: { data: BlockKitInput }) {
return <BlockKit data={data} theme="dark" />;
}theme is "light" (default) or "dark". It controls every color in the
render: text, surfaces, borders, buttons, chart palettes. It also sets
color-scheme so native form controls (selects, date pickers, scrollbars)
follow along.
The same payload, light then dark. Watch the chart palette and the button fills, not just the background:
Nightly build
Suite duration by shard
main · 3 checks passedView payload JSON
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Nightly build"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Branch:*\nmain"
},
{
"type": "mrkdwn",
"text": "*Tests:*\n214 passed"
}
]
},
{
"type": "divider"
},
{
"type": "data_visualization",
"title": "Suite duration by shard",
"chart": {
"type": "bar",
"axis_config": {
"categories": [
"1",
"2",
"3"
],
"y_label": "seconds"
},
"series": [
{
"name": "Duration",
"data": [
{
"label": "1",
"value": 62
},
{
"label": "2",
"value": 48
},
{
"label": "3",
"value": 71
}
]
}
]
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"action_id": "promote",
"style": "primary",
"text": {
"type": "plain_text",
"text": "Promote"
}
},
{
"type": "button",
"action_id": "revert",
"style": "danger",
"text": {
"type": "plain_text",
"text": "Revert"
}
},
{
"type": "button",
"action_id": "logs",
"text": {
"type": "plain_text",
"text": "View logs"
}
}
]
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Triggered from `main` · 3 checks passed"
}
]
}
]
}Nightly build
Suite duration by shard
main · 3 checks passedView payload JSON
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Nightly build"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Branch:*\nmain"
},
{
"type": "mrkdwn",
"text": "*Tests:*\n214 passed"
}
]
},
{
"type": "divider"
},
{
"type": "data_visualization",
"title": "Suite duration by shard",
"chart": {
"type": "bar",
"axis_config": {
"categories": [
"1",
"2",
"3"
],
"y_label": "seconds"
},
"series": [
{
"name": "Duration",
"data": [
{
"label": "1",
"value": 62
},
{
"label": "2",
"value": 48
},
{
"label": "3",
"value": 71
}
]
}
]
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"action_id": "promote",
"style": "primary",
"text": {
"type": "plain_text",
"text": "Promote"
}
},
{
"type": "button",
"action_id": "revert",
"style": "danger",
"text": {
"type": "plain_text",
"text": "Revert"
}
},
{
"type": "button",
"action_id": "logs",
"text": {
"type": "plain_text",
"text": "View logs"
}
}
]
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Triggered from `main` · 3 checks passed"
}
]
}
]
}The root paints its own background in the active theme's message-surface color, so a dark render sits correctly on any host page.
The stylesheet
All theme variables live in the one precompiled stylesheet:
import "react-blockkit/styles.css";Import it once, after any global reset (Tailwind preflight, normalize). The renderer's rules must come later in the cascade, or resets like button and list normalization will override them.
Styling the root
<BlockKit /> accepts a className that is appended to the root element. Use
it for sizing and placement (width, margin) rather than restyling block
internals:
import { BlockKit, type BlockKitInput } from "react-blockkit";
export function Preview({ data }: { data: BlockKitInput }) {
return <BlockKit className="message-preview" data={data} />;
}The root also carries stable data attributes for targeting from CSS or tests:
| Attribute | Value |
|---|---|
data-block-kit-root | Present on the root element |
data-theme | "light" or "dark" |
data-surface | "message", "modal", or "home" |
Block components accept className the same way when composed individually.