Skip to content

feat(input-area): add auto resize support#625

Open
nocdn wants to merge 3 commits into
cloudflare:mainfrom
nocdn:feat/input-area-auto-resize
Open

feat(input-area): add auto resize support#625
nocdn wants to merge 3 commits into
cloudflare:mainfrom
nocdn:feat/input-area-auto-resize

Conversation

@nocdn

@nocdn nocdn commented Jul 10, 2026

Copy link
Copy Markdown

Summary

Adds an optional autoResize prop to InputArea and its Textarea alias. When enabled, the textarea grows vertically with its content.

Changes

  • Resize the textarea on initial render and value changes
  • Preserve onChange, onValueChange, and forwarded ref behavior
  • Hide the manual resize handle while automatic resizing is enabled
  • Add unit tests and a documentation example
  • Add a minor changeset for @cloudflare/kumo

Implementation note

field-sizing: content was considered, but it can also change the textarea’s inline size. Since autoResize is intended to affect only vertical height, this implementation uses scrollHeight instead. This also avoids relying on field-sizing support in older browsers.

Testing

  • CI=true pnpm --filter @cloudflare/kumo exec vitest run --project=unit src/components/input/input.test.tsx
  • pnpm --filter @cloudflare/kumo typecheck
  • pnpm --filter @cloudflare/kumo lint

  • Reviews
    • bonk has reviewed the change
    • automated review not possible because: this focused component enhancement will receive normal PR review
  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:

Bartek Bak and others added 2 commits July 10, 2026 10:55
- No-op entirely when autoResize is off so a user's manual drag-resize
  is never clobbered by value changes; restore inline styles when the
  prop toggles off
- Add maxRows: clamp growth via computed line-height and flip
  overflow-y to auto past the clamp so content stays scrollable
- Account for box-sizing and borders when converting scrollHeight to
  an explicit height
- Re-measure on inline-size changes via ResizeObserver so rewrapped
  content keeps the correct height
- Avoid the React 18 SSR useLayoutEffect warning and honor React 19
  callback-ref cleanups

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mattrothenberg

Copy link
Copy Markdown
Collaborator

/bonk please review

@pkg-pr-new

pkg-pr-new Bot commented Jul 10, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/@cloudflare/kumo@625

commit: 211664e

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Docs Preview

View docs preview

Commit: 211664e

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor
Visual Regression Report — 2 changed, 0 unchanged

2 screenshot(s) with visual changes:

Dialog (Open)

23,408,621 px (93.43%) changed

Before After Diff
Before After Diff

Select (Open)

23,586,629 px (93.42%) changed

Before After Diff
Before After Diff

Generated by Kumo Visual Regression

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thorough PR! The implementation is solid — useTextareaAutoResize correctly accounts for box-sizing, borders, and width-driven rewraps. The React 19 callback-ref cleanup and SSR-safe useIsomorphicLayoutEffect are nice touches.

A couple of suggestions and one minor concern:

Performance: handleChange calls resize() on every input event, and the layout effect calls it again after the subsequent render. For controlled inputs this causes two forced synchronous layouts per keystroke. Relying solely on the layout effect for controlled components would halve that work. You could detect control mode (e.g. 'value' in inputProps) and skip the handleChange resize when controlled, since the layout effect already covers post-render measurement.

Edge case: getComputedStyle will typically resolve line-height to a pixel value, but if a browser or testing environment returns a unitless string like "1.5", parsePx("1.5") yields 1.5 and maxHeight collapses to 1.5 * maxRows pixels, which would clamp far too aggressively. A small safety check like Number.isNaN(parsePx(style.lineHeight)) ? parsePx(style.fontSize) * 1.2 : parsePx(style.lineHeight) would make the fallback more explicit.

Tests: Great coverage for the main paths. Consider adding a test that unmounts an autoResize textarea and verifies style.height and style.overflowY are cleared (the cleanup is implemented but not asserted).

None of these are blockers — the feature works correctly as-is. Nice work.

Comment thread packages/kumo/src/components/input/input-area.tsx Outdated
Comment thread packages/kumo/src/components/input/input-area.tsx Outdated
@ask-bonk

ask-bonk Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review submitted to PR #625.

Summary: The auto-resize implementation is solid with good edge-case handling (box-sizing, borders, width rewraps, React 19 refs, SSR). I left two non-blocking inline suggestions:

  1. Performanceresize() in handleChange + the post-render layout effect causes double forced layouts per keystroke for controlled inputs. Skipping the handleChange resize when controlled would eliminate the redundancy.
  2. Robustness — The line-height parsing assumes getComputedStyle always returns a pixel value. If any environment returns a unitless string, the maxRows clamp would be far too aggressive.

Overall approved with comments — no blockers.

github run

…ht parsing

- Skip the change-handler resize for controlled inputs; the post-render
  layout effect already re-measures, so this halves forced layouts per
  keystroke
- Treat a unitless computed line-height (e.g. "1.5") as a font-size
  multiplier instead of pixels so the maxRows clamp stays correct
- Add tests for unitless line-height and unmount style cleanup

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mattrothenberg

Copy link
Copy Markdown
Collaborator

/bonk I think I addressed your feedback! Please re-review

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior feedback has been addressed. Re-approving.

  • Performance: handleChange now guards resize() with !isControlled, eliminating the double forced synchronous layout per keystroke for controlled inputs.
  • Edge case: line-height parsing now correctly treats unitless values (e.g. '1.5') as font-size multipliers instead of raw pixels, and falls back to fontSize * 1.2 for 'normal'.
  • Tests: Added the unmount cleanup assertion verifying style.height and style.overflowY are cleared.

No blockers — nice work!

@ask-bonk

ask-bonk Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Re-reviewed and approved

All three items from the prior review were addressed:

  1. PerformancehandleChange now only calls resize() for uncontrolled inputs (!isControlled), avoiding the double forced synchronous layout on every keystroke for controlled components.
  2. Edge caseline-height parsing now handles unitless values (e.g. "1.5") by multiplying against fontSize, and falls back to fontSize * 1.2 for "normal".
  3. Tests — Added "clears inline styles on unmount" asserting style.height and style.overflowY are reset.

github run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants