Skip to content

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

@ardnew

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions