-
Notifications
You must be signed in to change notification settings - Fork 2.3k
React 19 Support
How to use Blueprint v6 with React 19, and how to migrate off the two components that don't support it.
Yes. Blueprint v6 supports React 19. For the cleanest install experience, use Blueprint v6.16.0 or newer, where the published@blueprintjs/* peer dependency ranges include React 19.
There is one application-code requirement: two deprecated components don't work under React 19 and must be replaced before they are removed in v7.
Everything else in Blueprint already works on React 19. An application that renders neither component directly needs only the install step below.
Only code that imports Popover or Overlay from @blueprintjs/core and renders them directly is affected. The fastest way to find every usage is to lint for it; see Finding usages.
Use Blueprint v6.16.0 or newer and upgrade any other @blueprintjs/* packages to the same version range.
npm install @blueprintjs/core@^6.16.0Note
Prior to v6.16.0, the published v6 peer dependency ranges only listed React 18. Installing those older versions under React 19 can surface a peer-dependency conflict. How it shows up depends on the package manager: npm fails the install with ERESOLVE, while Yarn and pnpm only print a warning and install anyway.
Installing before the peer-dependency bump (or on an older v6 minor)
While @blueprintjs/* still asks for react@18, installing under React 19 surfaces a peer-dependency conflict. How it shows up depends on the package manager: npm fails the install with ERESOLVE, while Yarn and pnpm only print a warning and install anyway.
npm. Overrides don't relax a declared peer range, so tell npm to proceed past the conflict:
npm install --legacy-peer-depsOptionally pin the React types so the whole tree resolves to v19, in package.json:
{
"overrides": {
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0"
}
}Yarn. The peer mismatch is only a warning, so the install already completes. To dedupe the React types to v19 (Yarn Classic), in package.json:
{
"resolutions": {
"@blueprintjs/core/@types/react": "^19.0.0",
"@blueprintjs/core/@types/react-dom": "^19.0.0"
}
}On Yarn Berry, packageExtensions in .yarnrc.yml can amend Blueprint's declared peer range and silence the warning.
pnpm. The peer mismatch is a warning. To silence it and pin the React types, in pnpm-workspace.yaml:
overrides:
"@types/react": "^19.0.0"
"@types/react-dom": "^19.0.0"
peerDependencyRules:
allowedVersions:
react: "19"
react-dom: "19"(On pnpm 9–10 these settings live under a "pnpm" key in package.json instead.)
Remove these once Blueprint's peer range includes React 19.
The application's own react, react-dom, @types/react, and @types/react-dom must also be upgraded to 19, following React's upgrade guide. Blueprint's types are compatible with @types/react@19, but the app-side React upgrade is a separate, application-owned step.
In short: everything in Blueprint works on React 19 except two deprecated components rendered directly by application code.
| Component | Works under React 19? | Replacement |
|---|---|---|
Popover |
No | PopoverNext |
Overlay |
No | Overlay2 |
| Everything else | Yes | — |
Both blockers fail for the same reason: they reach ReactDOM.findDOMNode, which React 19 removed. Legacy Popover depends on react-popper, which calls it internally; legacy Overlay drives a react-transition-group CSSTransition without a nodeRef, which falls back to the same call. In both cases, rendering the component throws at runtime with no prior warning.
These commonly used components and APIs already avoid the React 19 incompatibilities and need no migration:
-
Dialog,Drawer, andAlertare built onOverlay2. -
Select,MultiSelect,Suggest,Tooltip,ContextMenu, andDateInputare built onPopoverNext. - The imperative toaster (
OverlayToaster.create()) and the singletonContextMenuAPI render withcreateRootrather than the removedReactDOM.render.
Only direct use of Popover or Overlay requires migration.
Both components carry an @deprecated annotation, so editors and the @typescript-eslint/no-deprecated rule flag every usage:
// eslint.config.js
import tseslint from "typescript-eslint";
export default tseslint.config({
rules: { "@typescript-eslint/no-deprecated": "error" },
});@blueprintjs/eslint-plugin adds coverage for Blueprint-specific deprecations through its recommended config (blueprint.flatConfigs.recommended for flat config, or extends: ["plugin:@blueprintjs/recommended"] for the legacy format). Running the linter surfaces remaining usages.
PopoverNext is the modern equivalent of Popover, built on Floating UI instead of Popper.js. Its API is close to the legacy component's, but the positioning props changed with the engine, and a few defaults moved (see migration guide).
1. Swap the import and the tag.
// Before
import { Popover, type PopoverProps } from "@blueprintjs/core";
// After
import { PopoverNext, type PopoverNextProps } from "@blueprintjs/core";2. Update the props that changed.
| Legacy prop | PopoverNext | What to do |
|---|---|---|
modifiers |
middleware |
Popper modifiers became Floating UI middleware. Convert with popperModifiersToNextMiddleware() or rewrite by hand. |
modifiersCustom |
— | No direct equivalent. Fold any custom modifiers into the middleware prop. |
position |
placement |
Rename. position="auto" (and the old default) becomes no placement prop, which selects Floating UI's auto-placement. Convert with popoverPositionToNextPlacement(). |
minimal={true} |
animation="minimal" + arrow={false}
|
The single minimal flag split into an animation style and explicit arrow control. |
boundary="clippingParents" |
boundary="clippingAncestors" |
Value renamed. An Element or Element[] boundary passes through unchanged. |
shouldReturnFocusOnClose |
same prop, default changed | The default flipped from false to true. To keep the old behavior, set it explicitly: shouldReturnFocusOnClose={false}. |
Type names moved alongside the props: Placement/PopoverPosition → PopoverNextPlacement, and Boundary → PopoverNextBoundary.
A worked example:
// Before
import { Popover, PopoverPosition } from "@blueprintjs/core";
<Popover
position={PopoverPosition.BOTTOM_LEFT}
minimal={true}
boundary="clippingParents"
modifiers={{ offset: { options: { offset: [0, 8] } } }}
>
<Button text="Open" />
{content}
</Popover>;// After
import { PopoverNext } from "@blueprintjs/core";
<PopoverNext
placement="bottom-start"
animation="minimal"
arrow={false}
boundary="clippingAncestors"
middleware={{ offset: { mainAxis: 8 } }}
shouldReturnFocusOnClose={false}
>
<Button text="Open" />
{content}
</PopoverNext>;The migration helpers perform these conversions automatically:
import {
PopoverNext,
popoverPositionToNextPlacement,
popperModifiersToNextMiddleware,
PopoverPosition,
} from "@blueprintjs/core";
<PopoverNext
placement={popoverPositionToNextPlacement(PopoverPosition.BOTTOM_LEFT)}
middleware={popperModifiersToNextMiddleware({ offset: { options: { offset: [0, 8] } } })}
// ...
/>;Important
The default for shouldReturnFocusOnClose has changed. When left unset, focus now returns to the trigger on close where it previously did not. When migrating, set shouldReturnFocusOnClose={false} to preserve the old behavior.
A few legacy options don't carry over cleanly:
-
offsetas a function. Popper accepted a function foroffset; Floating UI doesn't. The converter drops it with a console warning. Rewrite it as a static{ mainAxis, crossAxis }object onmiddleware. -
arrowelement as a CSS selector string. Floating UI needs a real element, not a selector. In practice the arrow is now controlled with thearrowboolean, so this rarely matters. -
portalStopPropagationEventsis gone. It has been inert since React 17 changed event delegation. -
Placement vs. auto-placement are mutually exclusive. With a
placementset, PopoverNext usesflipto reposition when space is tight; with noplacement, it usesautoPlacement. The two cannot be combined.
Legacy Popover exposed a single popoverRef to the popover element, and some code reached the Popover class instance through a component ref. PopoverNext splits these into two:
import { PopoverNext, type PopoverNextRef } from "@blueprintjs/core";
const handle = useRef<PopoverNextRef>(null);
<PopoverNext
ref={handle} // imperative handle: handle.current?.reposition()
popoverRef={node => {
/* ... */
}} // DOM ref to the .bp6-popover element
/>;-
popoverRef(the prop) provides the DOM node, the element with theClasses.POPOVERclass. Use it wherever the popover element was previously read. -
The component
refresolves to aPopoverNextRef, an imperative handle whosereposition()method replaces the old class instance method. Other instance internals (for examplepopoverElement) are no longer reachable; use the two refs above instead.
A component that wraps Popover and forwards a popoverProps: PopoverProps prop can swap to PopoverNext without changing its own public API. Run the incoming props through popoverPropsToNextProps and spread the result:
import { PopoverNext, popoverPropsToNextProps, type PopoverProps } from "@blueprintjs/core";
function MyWidget({ popoverProps }: { popoverProps?: PopoverProps }) {
return <PopoverNext {...popoverPropsToNextProps(popoverProps)}>{/* ... */}</PopoverNext>;
}popoverPropsToNextProps applies the following transformations:
- It converts
position/placement,modifiers→middleware,minimal→animation/arrow, and theboundaryvalue, passing the rest through 1:1. - It pins
shouldReturnFocusOnClose: falseto preserve the legacy default (unless a value is supplied explicitly). - It drops
modifiersCustomandportalStopPropagationEvents, each with a development-mode warning. - The
onCloseit returns is a new function on each call, not the original reference.
Legacy Overlay is the second component that breaks on React 19, for the same reason: its CSSTransition falls back to ReactDOM.findDOMNode. Overlay2 is the React-19-safe replacement.
See the Overlay2 migration guide.
This migration applies only to code that renders Overlay directly. The components that used to wrap it (Dialog, Drawer, Alert) already use Overlay2.
-
v6.16.0+ includes React 19 in the published peer dependency ranges. Legacy
PopoverandOverlayremain available but stay deprecated; they are the only parts of Blueprint v6 that don't work under React 19. -
v7 removes legacy
PopoverandOverlay. At that point the next-generation components take the canonical names (PopoverNextbecomesPopover,Overlay2becomesOverlay), and React 19 is the supported baseline.
- FAQ
- 6.x Changelog
- 5.x Changelog
- 5.0 pre-release changelog
- 4.x Changelog
- v4.0 & v5.0 major version semantic swap
- v6.0 changes
- React 19 Support
- Spacing System Migration: 10px to 4px
- react-day-picker v8 migration
- HotkeysTarget & useHotkeys migration
- PanelStack2 migration
- Table 6.0 changes