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.

// 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 highest-leverage 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 .bp5-dark class

Blueprint dark mode is activated by the presence of the .bp5-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.

function ThemeToggle() {
  const [dark, setDark] = useState(
    () => window.matchMedia("(prefers-color-scheme: dark)").matches
  );
 
  useEffect(() => {
    document.documentElement.classList.toggle("bp5-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 .bp5-dark class on <html> propagates to all portaled children because portals inherit CSS from the document root. See blueprint-overlays for the OverlaysProvider setup.

Override CSS custom properties for runtime theming

Blueprint 5 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).

:root {
  --bp5-intent-primary: #2965cc;
  --bp5-intent-primary-hover: #2458b3;
}

Blueprint’s custom properties follow the --bp5-* 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 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:

.my-compact-table .bp5-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 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.

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 accessibility for the tradeoff.