TL;DR: Six React packages in our catalogue crossed a major version. We measured both sides of every jump with the same pipeline.
react-dropzoneshrank 62%, and we pinned the drop to the exact release that caused it.@tanstack/react-tablegrew 114%. The rest moved between 4% and 26%.
Why measure both sides
Most upgrade advice tells you what broke. Almost none tells you what it cost, because that means installing the old version too, and nobody keeps the old version around.
We maintain a catalogue of React UI packages where every figure is fetched rather than typed. Re-measuring it in August 2026 threw up an obvious follow-up question: six of these packages had crossed a major since the last check, so what did the upgrade actually change on disk?
Every number below comes from the same pipeline. Install the version alone, bundle export * from '<package>' with esbuild in esm, browser, minified, production mode, mark React external because your app already has it, gzip at level 9. That measures the whole public surface, so it is a ceiling rather than a typical cost. It is also identical on both sides of each jump, which is what makes the delta mean something.
The six jumps
| Package | From | To | Before | After | Change |
|---|---|---|---|---|---|
| react-dropzone | 14.3.5 | 20.1.1 | 16.7 kB | 6.3 kB | -62% |
| sonner | 1.7.4 | 2.0.8 | 9.3 kB | 9.6 kB | +4% |
| recharts | 2.15.1 | 3.10.1 | 134 kB | 158 kB | +18% |
| lucide-react | 0.475.0 | 1.34.0 | 144 kB | 172 kB | +19% |
| react-resizable-panels | 2.1.7 | 4.12.3 | 9.1 kB | 11.4 kB | +26% |
| @tanstack/react-table | 8.21.2 | 9.1.2 | 15.3 kB | 32.7 kB | +114% |
react-dropzone: six majors, and a 63% drop in one of them
This is the interesting one. Fourteen to twenty is six major versions, and the package came out substantially smaller. Measuring each step shows the drop is not gradual:
| Version | Size |
|---|---|
| 14.3.5 | 16.7 kB |
| 15.0.0 | 16.9 kB |
| 16.0.0 | 15.5 kB |
| 17.0.0 | 14.9 kB |
| 18.0.0 | 5.5 kB |
| 19.0.0 | 5.8 kB |
| 20.1.1 | 6.3 kB |
Almost all of it lands in a single release, and the v18 notes say why: it upgraded file-selector to v4, and "the full extension-to-MIME table is no longer bundled". A lookup table of every known file extension is exactly the kind of thing that is large, rarely needed in full, and invisible until someone measures.
That has a migration cost. If you relied on the extension-to-MIME mapping, you now pass it in yourself:
import { COMMON_MIME_TYPES } from 'file-selector/mime';
import { fromEvent } from 'file-selector';
<Dropzone getFilesFromEvent={event => fromEvent(event, COMMON_MIME_TYPES)} />
v18 also tightened types. FileWithPath now requires path and relativePath, so a plain File is no longer assignable and onDrop handlers typed as FileWithPath[] stop compiling. Type them as File[] instead:
// before
const onDrop = useCallback((accepted: FileWithPath[]) => { /* ... */ }, []);
// after
const onDrop = useCallback((accepted: File[]) => { /* ... */ }, []);
Two more steps have behaviour changes worth knowing. v19 stopped rejecting a whole batch when it exceeded maxFiles, and now accepts the files that fit, so onDropAccepted fires with a partial set where it previously fired with nothing. v20 is Node-only: minimum supported Node is now 22, which affects your CI image and nothing in the browser.
@tanstack/react-table: v9 stable, and twice the size
The largest relative growth in the set. v9 is a stable release of a long-running rewrite, and the release notes point at a per-framework migration guide rather than listing changes inline, which is a fair signal of how much moved.
Put 114% in perspective before reacting to it. The package went from 15.3 kB to 32.7 kB gzipped at full surface. That is real, and it is also small next to almost any application bundle. This is a growth worth understanding, not a growth worth blocking an upgrade over.
recharts: state moved, and Customized lost its props
Recharts 3 rewrote state management. The consequences are narrow but sharp: CategoricalChartState, which gave you access to recharts internals, no longer arrives in event handlers, and <Customized /> no longer receives chart state or props.
If your charts are declarative, you will likely notice nothing. If you built a custom overlay by reading internal state out of Customized, that is the code to rewrite, and the migration guide in the project wiki is where the replacement APIs are listed.
react-resizable-panels: ESM-only, then renamed to match ARIA
Two majors, each with a distinct kind of break.
v3 made the package ESM-only. That is a tooling change, not a code change, and it either does nothing to you or it stops your build outright depending on how your bundler and Jest configuration handle ESM dependencies.
v4 is a rename, and the reasoning is stated plainly: the names now follow web standards. PanelResizeHandle became Separator to match the ARIA separator role, and direction became orientation to match aria-orientation.
// v3
<PanelGroup direction="horizontal">
<Panel />
<PanelResizeHandle />
<Panel />
</PanelGroup>
// v4
<PanelGroup orientation="horizontal">
<Panel />
<Separator />
<Panel />
</PanelGroup>
v4 also widened size constraints to accept pixels, percentages and rem/em rather than percentages alone, which is the feature the rename came packaged with.
lucide-react: check you are not on 1.0.0
A footnote that will cost somebody an afternoon. Lucide's 1.0.0 release carries a warning from the maintainers that it was published unintentionally, and that 1.0.1 should be used instead. If a lockfile pinned 1.0.0 exactly, move it.
The 19% growth from 0.475.0 to 1.34.0 is mostly icons: the set keeps expanding, and the full-surface measurement counts every one of them. This is the entry where the ceiling is least like your real cost, because almost nobody imports the whole icon set. Three icons cost roughly three icons.
sonner: the quiet one
One to two moved 4%, from 9.3 kB to 9.6 kB, and the release notes describe it as mostly bug fixes. Of the six, this is the jump least likely to need any work, which is worth saying out loud: a major version number is a promise that something could break, not evidence that something did.
What we could not verify
framer-motion crossed from 12 to 13 in the same window. We measured 13.1.1 at 64.0 kB, but could not retrieve release notes for that major from the repository, and the 12.4.11 install failed in our harness, so there is no delta either.
Rather than reconstruct the migration from memory, we have left it out. Upgrade advice assembled from recollection is the exact failure mode that makes this kind of article untrustworthy, and one unverified entry would put the other six in doubt.