---
title: "Blueprint: Theming"
slug: "blueprint-theming"
category: "frontend"
tags: ["frontend", "blueprint", "react", "theming", "sass", "dark-mode", "custom-properties", "design-system"]
status: "stable"
last_updated: 2026-08-14
summary: "Override Blueprint Sass variables before importing the stylesheet; toggle .bp6-dark for dark mode; avoid patching compiled CSS directly."
related: ["[[frontend/blueprint]]", "[[frontend/blueprint-dialog]]", "[[frontend/blueprint-overlays]]", "[[frontend/blueprint-table]]", "[[frontend/css-custom-properties]]", "[[frontend/css]]", "[[frontend/react]]"]
---

## Overview

Blueprint ships a Sass-based theme. The correct extension point is overriding Sass variables before importing the CSS, or overriding CSS custom properties at runtime. Editing compiled `blueprint.css` directly or adding specificity hacks produces a theme that breaks on every package upgrade. This page covers the override model, dark mode toggling, and the patterns that survive a Blueprint version bump.

## Override Sass variables before the import, not after

Blueprint exposes its design tokens as Sass variables in `@blueprintjs/core/lib/scss/variables`. Override them in a `@use` statement before importing the CSS.

```scss
// styles/blueprint-theme.scss
@use "@blueprintjs/core/lib/scss/variables" with (
  $pt-intent-primary: #2965cc,
  $pt-intent-success: #0f9960,
  $pt-intent-warning: #d9822b,
  $pt-intent-danger:  #db3737,
  $pt-grid-size:      10px,
  $pt-border-radius:  3px,
  $pt-font-family:    "Inter", -apple-system, sans-serif
);

@import "@blueprintjs/core/lib/css/blueprint.css";
@import "@blueprintjs/icons/lib/css/blueprint-icons.css";
```

Import this file once at the app entry. All components in the render tree pick up the overridden variables because Sass compiles them into the resulting CSS before the browser parses it.

The critical mistake is importing Blueprint's CSS first, then trying to override variables afterward. Sass variables are compile-time; they cannot be overridden by later declarations.

## Set the primary intent color to match the brand

`$pt-intent-primary` drives all `Intent.PRIMARY` buttons, progress bars, form focus rings, and selected states. It is the most valuable variable to change for a brand retheme.

Choose a color that passes 4.5:1 contrast against white (`#ffffff`) for text on primary buttons. Use a contrast checker before committing. If the brand color fails contrast, use a darkened shade for `$pt-intent-primary` and reserve the bright brand color for decorative uses.

Provide the full intent set (`success`, `warning`, `danger`) whenever you provide `primary`. Blueprint's status system relies on all four being visually distinct. Changing primary and leaving the others at Blueprint defaults can produce a palette where two intents are nearly identical.

## Toggle dark mode with the `.bp6-dark` class

Blueprint dark mode is activated by the presence of the `.bp6-dark` class on a wrapping element. The typical placement is `document.documentElement` (the `<html>` tag) so that all portal-rendered overlays (Dialog, Popover, Tooltip) also receive the dark styles.

```tsx
function ThemeToggle() {
  const [dark, setDark] = useState(
    () => window.matchMedia("(prefers-color-scheme: dark)").matches
  );

  useEffect(() => {
    document.documentElement.classList.toggle("bp6-dark", dark);
  }, [dark]);

  return (
    <Switch
      checked={dark}
      label="Dark mode"
      onChange={(e) => setDark(e.target.checked)}
    />
  );
}
```

Persist the user preference in `localStorage`. Read it before the first render to avoid a flash of wrong theme. When Blueprint overlays are mounted with `OverlaysProvider`, the `.bp6-dark` class on `<html>` propagates to all portaled children because portals inherit CSS from the document root. See [[frontend/blueprint-overlays]] for the OverlaysProvider setup.

## Override CSS custom properties for runtime theming

Blueprint 6 exposes a subset of its tokens as CSS custom properties. Use them for runtime adjustments that cannot be done with Sass variables (e.g., a user-chosen accent color).

```css
:root {
  --bp6-intent-primary: #2965cc;
  --bp6-intent-primary-hover: #2458b3;
}
```

Blueprint's custom properties follow the `--bp6-*` namespace. Overriding them at runtime complements the Sass override, which is build-time. The Sass override sets the baseline; the CSS property override handles live user customization. See [[frontend/css-custom-properties]] for the general custom-property pattern.

## Do not patch compiled `blueprint.css` directly

Editing `node_modules/@blueprintjs/core/lib/css/blueprint.css` or copying it into the project as a local override breaks on every `npm install`. Avoid it entirely.

When a specific component needs a one-off style adjustment, scope it with a BEM modifier class applied to the component's wrapping element, and write the override in your own stylesheet with enough specificity to win:

```css
.my-compact-table .bp6-table-cell {
  padding: 2px 6px;
}
```

Blueprint does not guarantee class names between minor versions, so even this approach carries upgrade risk. Prefer Sass variable overrides for anything systemic. See [[frontend/css]] for the specificity rules.

## Use `FocusStyleManager` to suppress focus rings for mouse users

Blueprint renders keyboard-style focus rings on all interactive components by default. For apps where the audience is primarily mouse users and the design calls for no visible focus ring on click, enable Blueprint's `FocusStyleManager`.

```tsx
import { FocusStyleManager } from "@blueprintjs/core";

FocusStyleManager.onlyShowFocusOnTabs();
```

Call this once at app startup. It suppresses focus rings after a mouse click but restores them when the user navigates with the keyboard. Do not call this for apps with significant keyboard or screen-reader usage; the focus ring is a navigation aid, not just a visual effect. See [[frontend/accessibility]] for the tradeoff.

## Related

- [[frontend/blueprint]]
- [[frontend/blueprint-dialog]]
- [[frontend/blueprint-overlays]]
- [[frontend/blueprint-table]]
- [[frontend/css-custom-properties]]
- [[frontend/css]]
- [[frontend/react]]
