fix(TextArea): measure autosize off-screen, not on the live element - #1337
Conversation
`autoSize` sized the textarea by setting `height: auto` on the real element, forcing a layout, then restoring it — twice per keystroke. Every ancestor sharing the column re-laid out mid-keystroke, so a chat transcript above the input had its scroll viewport grow by the collapsed rows and its scroll offset moved; scroll anchoring undid that imperfectly, which reads as the conversation bouncing a pixel in time with the typing (CUB-4042). Both `TextArea` and `CommandTextArea` now share `useAutoSizeTextArea`, which measures an off-screen mirror. The mirror carries no padding or border and is sized to the live element's content width, so `scrollHeight` is the content height with none of the `rows` floor that also made one line count as two rows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 42f8853 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
📦 NPM canary releaseDeployed canary version 0.0.0-canary-5bbff30. |
🧪 Storybook is successfully deployed!
|
🏋️ Size limit report
Compared against main at 99843f4 — run 32478561414, 2026-08-21T11:44:06Z.To see which modules changed, download the size-limit-statoscope-report artifact from this run and open report.html. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7bd1d4a. Configure here.
A mirror *div* collapses a trailing newline, which is why autosize implementations built on one need a sentinel character. The mirror here is a textarea, so it lays the empty last row out exactly like the live field — asserted rather than assumed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Fixes CUB-4042 — the Cube Cloud chat UI jittering in time with typing.
What was happening
autoSizemeasured the textarea by mutating the live element:height: auto→ force a layout (getComputedStyle+scrollHeight) → restore. That ran twice per keystroke (chained ontoonChange, then again in the layout effect on the value), plus once more from its ownResizeObserver.height: autosizes a textarea from itsrowsattribute, so a textarea that has grown past its minimum — the everyday state of a chat prompt — collapses to one row for the duration of that forced layout. Every ancestor sharing the column re-lays out with it: the input box shrinks, and a scroll container above it grows by the same amount and has its scroll offset moved. Chrome's scroll anchoring normally undoes that, which is why nobody saw a 40px jump; what leaks through is the repair not landing exactly, and the whole conversation bounces a pixel and back on each keystroke.Frame-by-frame off the bug report's screen recording: the message column shifts
+1pxthen−1pxwithin one or two frames of every keystroke, while the input box itself does not move.What this changes
TextAreaandCommandTextAreanow shareuseAutoSizeTextArea, which measures a hidden mirror textarea instead of the live one. The mirror carries no padding or border and is sized to the live element's content width (scrollbar excluded), soscrollHeightis the content height and nothing else. No ancestor ever re-lays out, so there is nothing for scroll anchoring to repair.rows-and-font-metrics-derivedautoheight by CSSline-height. Where the line height is tighter than the font's natural line box, one line counted as two rows — anautoSizetextarea withrows={1}rendered a row taller than its content (visible today in the Cloud chat input). Measuring atheight: 0removes that floor; the row count now rounds, so a fractional line height (zoomed page, percentage preset) can no longer add a phantom row either.heightnow respectsbox-sizing. Padding and border are added only underborder-box, which is what<Root>sets globally.display: none, pre-layout) is skipped rather than measured at zero width, where every character wraps and the height pins tomaxRows.Behaviour change worth knowing
An
autoSizetextarea withrows={1}and a single line of text is now one row tall instead of two, wherever the line height is tighter than the font's line box. Consumers relying on the taller box should passrows={2}.Tests
New
*.browser.test.tsxspecs — real layout, which is the only place this is visible; jsdom reports 0 for every box:TextArea.browser.test.tsx— a single line is one row, growth and shrink,maxRowscap,rowsminimum.CommandTextArea.browser.test.tsx— a single line is one row, and typing does not move a sibling scroll container (the CUB-4042 regression). That test disablesoverflow-anchoron purpose: with anchoring left on, the browser hides the perturbation and the assertion would pass either way.Verified against the unfixed code: 4 of the 6 fail, the scroll one with
expected 512 to be 552— 40px of scroll offset lost on a single keystroke. Full suites green: 2058 jsdom tests, 114 browser tests.🤖 Generated with Claude Code
Note
Medium Risk
Layout math for shared
TextArea/CommandTextAreaautosize changes visible height (arows={1}single line is now one row, not two) and injects a DOM mirror; chat/prompt UIs will feel this immediately.Overview
Stops chat-input jitter (CUB-4042) by measuring
autoSizeheight off-screen instead of settingheight: autoon the live textarea (which collapsed grown prompts, re-laid out the column, and left scroll anchoring bouncing the transcript).TextAreaandCommandTextAreanow shareuseAutoSizeTextArea: a hidden mirror copies typography and content width, soscrollHeightis true content height with no ancestor layout. Row count is rounded from that measure (tightline-heightno longer turns one line into two), padding/border are added only underborder-box, and zero-width/pre-layout fields are skipped.Visible change:
autoSize+rows={1}with one line is now one row tall. Browser tests cover growth/shrink,maxRows, trailing newlines, and that typing does not move a sibling scroller.Reviewed by Cursor Bugbot for commit 42f8853. Bugbot is set up for automated code reviews on this repo. Configure here.