MIGRATION.md
visx 4 is the current major release. It modernizes React support, package entry points, browser targets, and the published ESM output. For most apps, the upgrade is:
@visx/* packages together to ^4.0.0.@visx/shape/lib/shapes/Bar with package-root imports like
@visx/shape.@types/react and, when needed, @types/react-dom using the
major version that matches your React version.withBoundingRects, attach its injected nodeRef to the DOM element you want
measured.propTypes, migrate those checks into your app.Upgrade every visx package in your application at the same time. Mixing visx 3 and visx 4 packages is not recommended because package entry points, peer dependency ranges, and internal package dependencies changed together.
npm install @visx/shape@^4 @visx/scale@^4
With Yarn, upgrade the visx packages your app depends on to the same major:
yarn up "@visx/*@^4.0.0"
React-based @visx/* packages declare react peer dependencies as ^18.0.0 || ^19.0.0. Packages
that depend on react-dom declare the same supported range for their react-dom peer dependency.
What you need to do:
react and react-dom to preact/compat, and configure
your package manager to satisfy the React 18/19 peer dependency range.React-based @visx/* packages now declare @types/react as an optional peer dependency instead of
bundling their own copy. Packages that touch React DOM APIs also declare optional @types/react-dom
peer dependencies.
What you need to do:
@types/react and, when needed, @types/react-dom using the
major version that matches your React version.visx packages no longer depend on prop-types, and the build no longer generates runtime
Component.propTypes from TypeScript definitions.
What you need to do:
prop-types only because a visx package installed it transitively, add
prop-types as a direct dependency.propTypes at runtime, migrate that validation into your app
or use TypeScript/static checks instead.Every package now publishes an exports field mapping package-root imports to types, import,
and require entries. This improves module resolution in modern bundlers and Node.js, but it also
restricts access to internal paths.
What you need to do: replace deep imports like @visx/foo/lib/bar with package-root imports
like @visx/foo. If a symbol you need is not exported from the package root, open an issue.
Most common component prop types are now exported from package roots as TypeScript-only exports. For
example, prefer importing AxisProps from @visx/axis instead of reaching into internal files.
Babel targets were bumped to modern browsers. IE11 and other legacy browsers are no longer supported.
What you need to do: if you still need IE11, stay on visx 3.
The published esm/ output now emits explicit .js extensions on relative imports and a nested
esm/package.json with "type": "module". Strict Node ESM consumers such as Vite SSR, Deno, and
edge runtimes should no longer hit ERR_MODULE_NOT_FOUND from extensionless visx ESM imports.
What you need to do: nothing, unless you previously pinned to an older alpha to work around ESM
issues. Upgrade all visx packages to ^4.0.0.
visx packages now use d3-shape v3 and d3-path v3 through @visx/vendor. visx preserves its
existing package-level behavior where compatibility shims were needed, but direct D3 imports are now
your app's responsibility.
What you need to do:
d3-shape or d3-path without declaring them in your own package.json, add
them as direct dependencies.@visx/vendor/d3-shape or @visx/vendor/d3-path, review the D3 v3 API
and type changes.@visx/bounds withBoundingRects requires nodeRefwithBoundingRects no longer falls back to ReactDOM.findDOMNode. The wrapped component receives a
nodeRef prop and must attach it to the DOM element that should be measured. This keeps
@visx/bounds compatible with React 19 and React strict-mode expectations.
What you need to do:
withBoundingRects, make sure the wrapped component accepts nodeRef and passes it to
the measured DOM element.nodeRef as a React ref for the element you measure.nodeRef, no action is needed.- function Tooltip({ rect, parentRect, children }) {
- return <div>{children}</div>;
+ type TooltipProps = Omit<WithBoundingRectsProps, 'nodeRef'> & {
+ nodeRef?: React.Ref<HTMLDivElement>;
+ };
+
+ function Tooltip({ rect, parentRect, nodeRef, children }: TooltipProps) {
+ return <div ref={nodeRef}>{children}</div>;
}
@visx/responsive measurement fixesParentSize and withParentSize now render a two-div structure to prevent infinite height growth
in flex and grid layouts. useParentSize now uses a callback ref internally and also accepts an
optional externalRef.
What you need to do:
parentRef.current from useParentSize, replace it with the returned node.ParentSize wrapper DOM structure, update them for the nested
measurement div.@visx/xychart axis loading behaviorBaseAxis no longer renders until at least one data series with non-empty data has been
registered in DataContext. Previously, on initial render the axis could use the fallback
scaleLinear() domain [0, 1], causing tickFormat to receive incorrect intermediate values
before real data loaded.
What you need to do:
<Axis /> from
@visx/axis directly with your own scale until your data is ready, then switch to the xychart
<Axis />.@visx/xychart withRegisteredData was removedThe internal withRegisteredData enhancer was removed from @visx/xychart. Series components now
register their data directly instead of using that higher-order component.
What you need to do: most consumers do not need to change code. If you imported
@visx/xychart/lib/enhancers/withRegisteredData directly, migrate to the public xychart series
components and package-root exports.
@visx/responsive, @visx/text, @visx/xychart, and @visx/shape no longer declare direct
lodash or @types/lodash dependencies. Internal debounce, memoize, and chunk usage has been
replaced with small local helpers. Public visx APIs are intended to behave the same.
What you need to do: if your app imported lodash without declaring it in your own
package.json, add lodash as a direct dependency. It may have worked before only because a visx
package installed it transitively.
This note is for contributors or consumers building the visx repo itself. The repository now uses Yarn 4 via Corepack and requires Node 24 or newer.
What you need to do: if you only install published @visx/* packages, no action is needed. If
you build the repo locally, use Node 24+ and run Yarn through Corepack.
Upgrade all @visx/* packages to ^4.0.0. The stable release includes all alpha changes listed
below, plus the final React 18/19 peer dependency range and Dependabot cleanup from the final alpha
publishes.
If you installed 4.0.0-alpha.10, 4.0.0-alpha.12, or 4.0.0-alpha.13, upgrade immediately. Those
alpha releases partially failed and left some packages unpublished or out of sync.
The notes below are preserved for users who tested the v4 alpha series. Stable upgrade guidance above supersedes any older alpha-specific instructions.
Internal only: updated transitive brace-expansion lockfile entries to resolve a Dependabot
security alert. No consumer-facing visx API changes are expected.
Internal only: updated demo, test, release, and build-tool dependencies to resolve Dependabot security alerts. No consumer-facing visx API changes are expected.
React-based @visx/* packages now declare react peer dependencies as ^18.0.0 || ^19.0.0.
Packages that depend on react-dom declare the same supported range for their react-dom peer
dependency. Optional @types/react and, where applicable, @types/react-dom peer dependencies use
the same supported major-version range.
What you need to do:
@types/react and, when needed, @types/react-dom using the
major version that matches your React version.react and react-dom to preact/compat, and configure
your package manager to satisfy the React 18/19 peer dependency range.Re-publish of alpha.12 and alpha.13, which partially failed (see below). No intended code changes relative to alpha.12 — all packages are now published at the same version.
What you need to do: if you installed any @visx/* packages at 4.0.0-alpha.12 or
4.0.0-alpha.13, upgrade to alpha.14 to ensure all packages are in sync.
@visx/responsive, @visx/text, @visx/xychart, and @visx/shape no longer declare direct
lodash or @types/lodash dependencies. Internal debounce, memoize, and chunk usage has been
replaced with small local helpers. Public visx APIs are intended to behave the same, including the
existing debounce options in @visx/responsive.
What you need to do:
lodash without declaring it in your own package.json: add lodash as
a direct dependency. It may have worked before only because a visx package installed it
transitively.This release also fixes the XYChart docs/demo example so it passes its known width into XYChart.
No consumer code changes are needed for that docs-only fix.
⚠️ Do not use this release. An npm provenance transparency log conflict caused lerna publish
to fail partway through. Some packages were never published at this version while the rest were,
resulting in a version mismatch across the monorepo. Use alpha.14 instead.
⚠️ Do not use this release. An npm provenance transparency log conflict caused lerna publish
to fail partway through. Some packages were never published at this version while the rest were,
resulting in a version mismatch across the monorepo. Use alpha.14 instead.
Re-publish of alpha.10 which partially failed (see below). No code changes — all packages are now published at the same version.
What you need to do: if you installed any @visx/* packages at 4.0.0-alpha.10, upgrade to
alpha.11 to ensure all packages are in sync.
⚠️ Do not use this release. A sigstore transparency log conflict caused lerna publish to fail
partway through. 18 packages were never published at this version while the rest were, resulting in
a version mismatch across the monorepo. Use alpha.11 instead.
Five packages (@visx/vendor, @visx/point, @visx/scale, @visx/curve, @visx/mock-data) were
stuck on pre-alpha.2 versions and still shipped ESM output without .js extensions or the
esm/package.json "type": "module" marker. This release force-publishes every package so the
alpha.2 ESM fix (#1976) is present across the board.
What you need to do: if you hit ERR_MODULE_NOT_FOUND in strict ESM environments (Vite SSR,
Deno, edge runtimes) with any of those five packages, upgrading to this release resolves it.
@visx/responsive useParentSize external ref supportuseParentSize now accepts an optional externalRef in its config object. If provided, the hook
forwards the observed DOM node to both its internal state and the external ref, eliminating the need
for a wrapper div when you already have a ref on the container element.
const myRef = useRef<HTMLDivElement>(null);
const { parentRef, width, height } = useParentSize({ externalRef: myRef });
return <div ref={parentRef}>...</div>;
// myRef.current is also set to the div element
Both RefObject and callback refs are supported.
What you need to do:
externalRef argument is unchanged.parentRef: you can now remove
the wrapper and pass your ref directly via the externalRef option.@visx/responsive ParentSize flex/grid infinite growth fixParentSize and withParentSize now render a two-div structure to prevent infinite height growth
in flex and grid layouts (#881,
#1014).
Previously, the component rendered a single wrapper <div style="width: 100%; height: 100%">. In
flex or grid containers, a child SVG's intrinsic height could grow the wrapper, triggering
ResizeObserver in a feedback loop.
The new structure uses an outer <div style="width: 100%; height: 100%; position: relative"> and an
inner <div style="position: absolute; top: 0; right: 0; bottom: 0; left: 0; overflow: hidden">
that holds the ResizeObserver and children. Since the inner div is absolutely positioned, children's
intrinsic sizes cannot grow the outer container.
What you need to do:
className, style,
and all other HTML attributes still apply to the outer div.parentSizeStyles prop: it still works and applies to the outer
div, but position: relative is prepended to ensure the inner absolutely-positioned measurement
div works correctly. If you explicitly set position in your custom styles, your value takes
precedence.withParentSize: the same two-div structure applies. The container div is no
longer a single <div style="width: 100%; height: 100%">.@visx/responsive useParentSize callback ref fixuseParentSize now uses a callback ref internally instead of useRef. This fixes a bug where the
hook could permanently report 0×0 dimensions because parentRef.current was null when the
useEffect first ran, and the dependency array never re-fired once the ref attached to the DOM
(#1816).
The returned parentRef is now a callback ref ((node: T | null) => void) instead of a
RefObject<T | null>. A new node property is also returned for direct access to the observed DOM
element.
What you need to do:
Most consumers: nothing — <div ref={parentRef}> works with both RefObject and callback
refs, and ParentSize component users are unaffected.
If you access parentRef.current directly: replace with the new node return value.
- const { parentRef, width, height } = useParentSize();
- console.log(parentRef.current);
+ const { parentRef, node, width, height } = useParentSize();
+ console.log(node);
@visx/xychart axis rendering fix (breaking)BaseAxis no longer renders until at least one data series with non-empty data has been
registered in DataContext. Previously, on initial render the axis could use the fallback
scaleLinear() domain [0, 1], causing tickFormat to receive incorrect intermediate values
before real data loaded (#1975).
This is a behavior change: axes that previously rendered immediately (showing 0–1 ticks while
data was loading) will now render null until real data is available.
What you need to do:
<Axis /> from @visx/axis directly with your own scale until your data is
ready, then switch to the xychart <Axis />.Thank you wildseansy for the fix #1979
Internal only: replaced ts-node with tsx (esbuild-based) for faster TypeScript script execution.
No consumer-facing changes.
@types/react and @types/react-dom as peer dependenciesReact-based @visx/* packages now declare @types/react as a peer dependency instead of
bundling their own copy. That way your app supplies a single version and you avoid duplicate or
conflicting installs.
@visx/bounds, @visx/tooltip, @visx/xychart, and the @visx/visx meta-package all declare
@types/react-dom as an optional peer directly (bounds and tooltip type DOM-touching APIs; xychart
consumes tooltip and so transitively needs react-dom at runtime; the umbrella matches the pattern of
its DOM-touching members).
@visx/brush and @visx/wordcloud also declare @types/react as an optional peer so consumers
installing either package directly get the same type-dependency signal as the rest of the
React-based @visx/* packages.
The peers are marked optional via peerDependenciesMeta, so non-TypeScript consumers will not see
missing-peer warnings.
What you need to do:
React 18 or 19 (TypeScript users): add @types/react as a dev dependency matching your React
version:
yarn add -D @types/react
If you use @visx/bounds, @visx/tooltip, @visx/xychart, or the @visx/visx meta-package,
also install @types/react-dom:
yarn add -D @types/react-dom
Use the major versions that align with your react / react-dom versions.
Non-TypeScript consumers: no action needed.
If you see TypeScript errors about missing react types after upgrading, install the appropriate
@types/react (and @types/react-dom when using @visx/bounds, @visx/tooltip, @visx/xychart,
or @visx/visx) in your application.
esm/ outputThe published esm/ output now emits explicit .js extensions on relative imports and a nested
esm/package.json with "type": "module". Strict Node ESM consumers (Vite/react-router SSR, Deno,
edge runtimes) previously failed with ERR_MODULE_NOT_FOUND when resolving visx's ESM entry.
What you need to do: nothing — this is a bugfix. If you were pinned to 4.0.0-alpha.1
specifically to avoid a different ESM issue, you can upgrade safely.
Release-plumbing only: prerelease bump configuration and CI permissions for release PR comments. No consumer-facing changes.
The React 19 cut. This is the release that drops legacy React support and modernizes the build targets.
Every @visx/* package now uses the automatic JSX transform and imports React symbols as direct
named imports or type-only imports (no more React.ReactNode namespace access). Peer dependency
ranges were widened to ^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0.
What you need to do:
@visx/shape/lib/shapes/Bar), prefer the package
root (@visx/shape). Deep imports are no longer the blessed API surface and may break in a future
alpha. Every package now declares exports so Node's module resolver will honor the root entry.Babel targets were bumped to modern browsers. IE11 and other legacy browsers are no longer supported.
What you need to do: if you still need IE11, stay on 3.x.
exports fieldEvery package now publishes an exports field mapping . to types, import, and require
entries. This improves module resolution in modern bundlers and Node.js, but does restrict access to
internal paths.
What you need to do: replace any deep imports (@visx/foo/lib/bar) with root imports
(@visx/foo). If a symbol you need isn't re-exported from the root, open an issue.
Internal only — Lerna was upgraded to v9, publishing now uses OIDC trusted publishing, and unused
ansi-* resolutions were removed from the root. No consumer impact.
@visx/demo (internal)Upgraded to React 19, Next.js 15, and @react-spring/web v10. Affects contributors only.