---
title: Theming
description: Light and dark mode, the --sbk-* CSS custom properties, fonts, and isolation from your app's global styles.
seo:
  title: "Slack Block Kit theming and dark mode"
---

Every color in the stylesheet is a `--sbk-*` CSS custom property, measured from Slack's own Block Kit
Builder. Dark mode is a matter of which values are active. You rarely need to override them, but
they're there when you do.

<Preview
  payload={{
    blocks: [
      { type: "section", text: { type: "mrkdwn", text: "Matches Slack in *light* and *dark*." } },
    ],
  }}
/>

## Light and dark mode

By default, dark values apply under `prefers-color-scheme: dark`: the page follows the visitor's OS
setting, same as Slack's own desktop client.

To force a theme regardless of OS preference, set `data-theme` on `<html>` (or any ancestor of your
Block Kit content):

```html
<html data-theme="dark"></html>
```

```html
<html data-theme="light"></html>
```

`data-theme="light"` also works as an escape hatch to opt an element back out of dark mode even when
the OS prefers it.

### The theme prop

`<BlockKitProvider theme="light" | "dark">` sets `data-theme` on a wrapper `<div>` around its children,
and on the menus, tooltips and dialogs it opens outside that wrapper. It's useful for previewing both
themes side by side on the same page, independent of the OS setting:

```tsx
<div style={{ display: "flex", gap: 16 }}>
  <BlockKitProvider theme="light">
    <Message blocks={blocks} />
  </BlockKitProvider>
  <BlockKitProvider theme="dark">
    <Message blocks={blocks} />
  </BlockKitProvider>
</div>
```

Leave `theme` unset to follow the page's own `data-theme`/`prefers-color-scheme` instead.

### Slack's other dark themes

Slack offers several dark themes; the dark values follow its default one. Callout backgrounds and
area chart fills in dark mode are derived from that palette rather than measured. To match another
theme, override the tokens under `[data-theme="dark"]`:

```css
[data-theme="dark"] {
  --sbk-callout-green-bg: #1f3a2c;
  --sbk-chart-area-1: #5c3320;
}
```

:::note
A modal opened through `views.open`/`views.push` renders in a nested provider that doesn't inherit
`theme`. It follows the surrounding page instead, matching how a real Slack modal always uses the
workspace's theme rather than something set per-message.
:::

## CSS custom properties

The full set lives in `base.css` and `Message.css`. Override any of them after importing the
stylesheet to restyle without touching the components. The main tokens:

| Prop | Type | Default | Description |
| - | - | - | - |
| `--sbk-font?` | `string` | `"Slack-Lato", Lato, sans-serif` | Body font. See Fonts below. |
| `--sbk-font-mono?` | `string` | `"Slack-Roboto-Mono", "Roboto Mono", monospace` | Code font. |
| `--sbk-text?` | `color` | - | Primary text color. |
| `--sbk-muted?` | `color` | - | Secondary/muted text, e.g. timestamps. |
| `--sbk-bg?` | `color` | - | Surface background (message, modal, Home tab). |
| `--sbk-link?` | `color` | - | Link and mention text color. |
| `--sbk-mention-bg?` | `color` | - | Resolved user/channel mention background. |
| `--sbk-primary?` | `color` | - | Primary button background (e.g. modal Submit). |
| `--sbk-danger?` | `color` | - | Danger-styled button background. |
| `--sbk-border?` | `color` | - | Default hairline border color. |
| `--sbk-divider?` | `color` | - | Divider block color. |
| `--sbk-code-text?` | `color` | - | Inline code and fenced code block text. |
| `--sbk-code-bg?` | `color` | - | Inline code and fenced code block background. |
| `--sbk-focus-ring?` | `color` | - | Keyboard focus ring and focused input border. |
| `--sbk-menu-bg?` | `color` | - | Dropdown/select menu background. |
| `--sbk-tooltip-bg?` | `color` | - | Tooltip background. |
| `--sbk-callout-green-bg?` | `color` | - | Callout background, one per `background_color`: green, blue, red, yellow, purple, gray. |
| `--sbk-chart-area-1?` | `color` | - | Area chart fill, one per series color: `--sbk-chart-area-1` to `-4`. |

```css
:root {
  --sbk-primary: #1264a3;
  --sbk-primary-hover: #0b4c80;
}
```

## Fonts

Slack sets body text in Lato and code in Roboto Mono, but Slack's own font files aren't
redistributable. Install the open-source builds from Fontsource instead:

```package-install
@fontsource/lato @fontsource/roboto-mono
```

```tsx main.tsx
import "@fontsource/lato/400.css";
import "@fontsource/lato/400-italic.css";
import "@fontsource/lato/700.css";
import "@fontsource/lato/900.css";
import "@fontsource/roboto-mono/400.css";
```

The stylesheet asks for `Slack-Lato` first, then falls back to plain `Lato`, so if you're already
serving Lato yourself (a CDN, self-hosted `@font-face`), you can skip installing Fontsource: any
`font-family: Lato` declaration is picked up.

## Isolation from your app's global CSS

Every component's root carries an `sbk-root` class (portalled dialogs and tooltips get it too), and
the stylesheet rolls everything under it back to the browser's defaults before applying its own rules:

```css
:is(.sbk-root, .sbk-confirm__overlay, .sbk-tooltip)
  :where(:not(svg, svg *, img, video, canvas, iframe, embed, object)) {
  all: revert;
}
```

This means global resets in your app (Tailwind's preflight, a docs theme's heading font, a blanket
`* { min-width: 0 }`) don't leak into how a block renders, and the reverse: block styles don't leak
into your app either, since everything is scoped under `.sbk-root`. You don't need to configure
anything for this; it's automatic once you import `@nkootstra/block-kit/styles.css`.

## Related

**[Installation](/installation)**

Import the stylesheet and fonts for the first time.

**[Server rendering](/guides/server-rendering)**

Setting data-theme before hydration avoids a light/dark flash.
