Skip to content

textarea: Bottom-anchored textarea shifts upward after multiline submit #993

Description

@ardnew

Description

When a bottom-aligned dynamic-height bubbles/v2/textarea collapses from N>1 lines to N=1 after submit/reset, it jumps upward and leaves N-1 blank lines below it. Single-line submits do not show this bug: the textarea stays anchored to the bottom row.

Setup

Please complete the following information along with version numbers, if applicable.

  • CachyOS (rolling)
  • Bash 5.3.9
  • Alacritty 0.17.0
  • en_US.UTF-8

To Reproduce

Steps

  1. Run the repro Go command attached below, e.g., go run ..

  2. Resize the terminal so it is fairly short, or submit a few entries until the textarea is resting on the bottom row.

  3. Confirm the non-buggy case: enter a single line (N=1) and press Ctrl+s (do NOT insert a newline with Enter).

    • Observe: The printed entry appears above, and the empty textarea remains anchored to the bottom:
    foo
    ┃  1
    
  4. To demonstrate the issue, enter N lines (N>1) using Enter for newlines, then press Ctrl+S.

    • Observe the difference: after the multiline submit (N=3), the textarea resets to one line but moves upward, leaving N-1=2 blank rows below it instead of staying on the bottom row (if you submit N=4 lines, there will be N-1=3 blank lines underneath; or if N=50, then N-1=49 blank lines; etc.):
    foo
    bar
    baz
    ┃  1
    
    
    
Controls
  • Enter: insert newline
  • Ctrl+S: submit
  • Esc or Ctrl+C: quit

Source Code

  • go.mod
module textarea-bottom-bug

go 1.26.1

require (
	charm.land/bubbles/v2 v2.1.0
	charm.land/bubbletea/v2 v2.0.6
)
  • main.go
package main

import (
	"fmt"
	"os"

	"charm.land/bubbles/v2/textarea"
	tea "charm.land/bubbletea/v2"
)

type model struct {
	ta textarea.Model
}

func newModel() model {
	ta := textarea.New()
	ta.DynamicHeight = true
	ta.MinHeight = 1
	ta.MaxHeight = 8
	ta.Focus()
	return model{ta: ta}
}

func (m model) Init() tea.Cmd {
	return textarea.Blink
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.WindowSizeMsg:
		m.ta.SetWidth(msg.Width)
		return m, nil

	case tea.KeyPressMsg:
		switch msg.String() {
		case "ctrl+c", "esc":
			return m, tea.Quit

		case "ctrl+s":
			entry := m.ta.Value()
			m.ta.Reset()
			return m, tea.Batch(
				tea.Printf("%s", entry),
				m.ta.Focus(),
			)
		}
	}

	var cmd tea.Cmd
	m.ta, cmd = m.ta.Update(msg)
	return m, cmd
}

func (m model) View() tea.View {
	v := tea.NewView(m.ta.View())
	if c := m.ta.Cursor(); c != nil {
		v.Cursor = c
	}
	return v
}

func main() {
	if _, err := tea.NewProgram(newModel()).Run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

Expected behavior
I expected the textarea to remain anchored to the bottom of the screen in both cases: N=1 and N>1.

Screenshots

  • Single line (N=1)
Image
  • Multiline (N=3)
Image

Additional context
N/A

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions