Skip to content

Conversation

@sfc-gh-nbellante
Copy link
Contributor

@sfc-gh-nbellante sfc-gh-nbellante commented Jan 8, 2026

Describe your changes

Improved the ChatInput component to dynamically switch between inline and stacked layouts based on content length. The component now:

  1. Starts in inline mode (buttons and textarea on same row)
  2. Switches to stacked mode when text fills available width
  3. Stays in stacked mode when text is partially deleted (still has content)
  4. Returns to inline mode only when all text is cleared

This creates a more responsive experience that adapts to the user's input without jarring layout shifts.

Screenshot or video (only for visual changes)

Kapture 2026-01-12 at 09 59 39

Testing Plan

  • Added comprehensive E2E tests in st_chat_input_test.py that verify the dynamic layout transitions
  • Tests cover all key scenarios: initial inline state, transition to stacked with long text, maintaining stacked with partial text, and returning to inline when cleared
  • The implementation uses efficient text measurement with canvas to avoid performance issues

Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

@snyk-io
Copy link
Contributor

snyk-io bot commented Jan 8, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 8, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-13546/streamlit-1.52.2-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-13546.streamlit.app (☁️ Deploy here if not accessible)

Copy link
Contributor Author

sfc-gh-nbellante commented Jan 8, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@sfc-gh-nbellante sfc-gh-nbellante force-pushed the fixit/ghost-button-colors branch from 1f97bec to 30542a4 Compare January 8, 2026 20:56
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/chat-input-dynamic-stacked-layout branch from 01add8e to 1d41a1c Compare January 8, 2026 20:56
@sfc-gh-nbellante sfc-gh-nbellante marked this pull request as ready for review January 8, 2026 20:56
@sfc-gh-nbellante sfc-gh-nbellante marked this pull request as draft January 8, 2026 21:34
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the fixit/ghost-button-colors branch from 30542a4 to c499bc6 Compare January 9, 2026 03:10
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/chat-input-dynamic-stacked-layout branch from 1d41a1c to b42d84f Compare January 9, 2026 03:10
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the fixit/ghost-button-colors branch from c499bc6 to adb9c8f Compare January 9, 2026 03:15
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/chat-input-dynamic-stacked-layout branch 2 times, most recently from c644a77 to 79baf6a Compare January 9, 2026 03:17
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the fixit/ghost-button-colors branch 2 times, most recently from 08b362e to 558aa1f Compare January 9, 2026 03:26
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/chat-input-dynamic-stacked-layout branch from 79baf6a to 9d06611 Compare January 9, 2026 03:26
@sfc-gh-nbellante sfc-gh-nbellante changed the base branch from fixit/ghost-button-colors to graphite-base/13546 January 9, 2026 03:53
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/chat-input-dynamic-stacked-layout branch from 9d06611 to 8e88e79 Compare January 9, 2026 03:53
@sfc-gh-nbellante sfc-gh-nbellante changed the base branch from graphite-base/13546 to develop January 9, 2026 03:53
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/chat-input-dynamic-stacked-layout branch from 8e88e79 to b4f3438 Compare January 11, 2026 22:53
@sfc-gh-nbellante sfc-gh-nbellante added security-assessment-completed Security assessment has been completed for PR change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Jan 12, 2026
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/chat-input-dynamic-stacked-layout branch 2 times, most recently from e4fc286 to 01bbe71 Compare January 12, 2026 20:11
return
}

// Small delay to allow textarea to mount and get proper dimensions
Copy link
Collaborator

Choose a reason for hiding this comment

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

issue: There isn't a guarantee that this the chat input ref will exist after 1 microtask (what this setTimeout is attempting to solve for). You may be able to fix this by make the ref a callback function, which would guarantee that it exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call on the timing issue. I tried the callback ref approach but TextArea only accepts RefObject for inputRef, not callback refs. :(

Ended up using useLayoutEffect instead since it runs synchronously after DOM mutations so the ref is guaranteed to be set. Removed the setTimeout entirely.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah maybe I spoke too soon, I think this broke the case I was trying to fix here :/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nvm, I think we good now.

Implements ChatGPT-style dynamic layout switching for chat input:

- Starts in inline mode: [+] [textarea flex:1] [mic] [send]
- Switches to stacked mode when text fills available width:
  - Full-width textarea on top row
  - Buttons row below with + on left, mic/send on right

Key changes:
- Add isStacked state to track layout mode
- Use canvas text measurement to detect when text fills width
- Refocus textarea and restore cursor position on mode transition
- Unified layout structure for both modes (removes old simple/extended split)
- Simplify refocus effect to always restore focus after layout transition
  (layout only changes due to user typing, so they always have focus)
- Remove unnecessary value dependency from refocus effect
- Add canvas ref cleanup on unmount for garbage collection
- Improve E2E test efficiency by using fill() instead of loop-based backspace
- Add negative assertion to verify short text stays in inline mode
Track previous isStacked value to detect actual layout transitions
instead of just checking mount state. This prevents the textarea from
stealing focus when the page initially loads.
- Use flexbox align-items: center for proper vertical alignment
- Reduce container padding to md (12px) for 58px total height
- Reduce textarea padding to twoXS (4px)
- Add alignItems: center to inline textarea wrapper
- Fix auto-focus on page load by tracking actual layout transitions
Instead of rendering UITextArea in different DOM positions (which causes
remount and focus loss), keep it at a fixed position and use CSS flex
properties (order, width, flex-wrap) to visually move it above buttons
when in stacked mode. This eliminates the need for manual focus/cursor
restoration after layout transitions.
- Fix textarea alignment after audio recording/submission by using
  height: auto when not in extended mode
- Re-measure textarea width when recording ends to ensure stacked
  layout detection works correctly after audio submission
- Add e2e tests for layout alignment and stacking after audio events
- Move waveform inline with cancel/approve buttons during recording
- Adjust waveform height to match button size for consistent layout
- Add type=button to file upload button to fix click handling
Use useLayoutEffect instead of setTimeout to measure textarea dimensions.
This provides deterministic timing as useLayoutEffect runs synchronously
after DOM mutations, guaranteeing the ref exists when we measure.
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/chat-input-dynamic-stacked-layout branch from 468d892 to c3d8430 Compare January 13, 2026 00:47
@sfc-gh-nbellante sfc-gh-nbellante merged commit 9891fd4 into develop Jan 13, 2026
45 checks passed
@sfc-gh-nbellante sfc-gh-nbellante deleted the feat/chat-input-dynamic-stacked-layout branch January 13, 2026 03:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users security-assessment-completed Security assessment has been completed for PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants