Skip to content

Inline Viewport Mode

tui-lipan supports rendering in an inline viewport instead of taking over the terminal with an alternate screen. The terminal history remains intact because inline mode does not enter the alternate screen.

Surface Modes

The app surface mode determines how the viewport occupies terminal space.

ModeBuilderBehavior
Fullscreen.fullscreen() (default)Takes over the full terminal using the alternate screen.
Ephemeral.inline_ephemeral(height)Inline viewport intended for short-lived sessions (e.g., a list picker).
Transcript.inline_transcript(height)Inline viewport intended for transcript-friendly sessions (e.g., a chat CLI).

Viewport Height

Every inline builder accepts either a fixed row count or an InlineHeight policy:

HeightBehavior
8 / InlineHeight::Fixed(8)Fixed viewport of 8 rows (clamped to at least 1).
InlineHeight::auto()The viewport follows the content's measured height every frame, capped only by the terminal height.
InlineHeight::auto_capped(12)Content-sized, but never taller than 12 rows.

Auto height removes the guesswork of picking a row count: the framework measures the view at the current terminal width after every layout change and grows or shrinks the viewport to match, so widgets are never clipped by a too-small fixed height.

rust
App::new()
    .inline_ephemeral(InlineHeight::auto())
    .mount(MyPicker)
    .run()

Notes on auto height:

  • The measured height is the content's natural (minimum) height at the current width; flexible fillers collapse to their minimum.
  • If the content is taller than the terminal (or the cap), the layout keeps its natural height and the viewport shows its top rows, cleanly clipping the bottom. The layout is deliberately not squeezed into the smaller viewport, which would trigger the stack overflow policy (wrapped text truncates, children hide from the top). Apps that want scrolling instead of clipping should give a ScrollView an explicit height.
  • Growing the viewport near the bottom of the screen scrolls host output up, exactly like printing lines would.
  • On host terminal resize the visible frame stays coherent, but rows the emulator moved into scrollback during its own reflow can remain there as stale copies. This is inherent to inline TUIs (fzf and chat CLIs show the same residue); no app can edit the emulator's scrollback without erasing all of it.

Basic Setup

Ephemeral Mode

Best for tools that provide a temporary UI and then exit, leaving only the exit_view in scrollback.

rust
App::new()
    .inline_ephemeral(8)
    .mount(MyPicker)
    .exit_view(|_component, ctx| {
        Text::new(format!("Selected: {}", ctx.state.selection)).into()
    })
    .run()

Transcript Mode

Best for chat-style apps that append messages to terminal history while running.

rust
App::new()
    .inline_transcript(12)
    // .inline_transcript_with_startup(12, InlineStartupPolicy::ClearHost)
    .mount(ChatApp)
    .run()

Mouse Behavior

ModeMouse defaultBehavior
FullscreenEnabledWheel events delivered to app; terminal scrollback inactive
InlineDisabledNative terminal scrolling preserved by default

Enable mouse in inline mode when your app needs wheel events:

rust
App::new().inline_ephemeral(8).mouse(true).mount(Root).run()

Runtime mouse control:

rust
ctx.mouse_capture_enabled()      // Current state
ctx.set_mouse_capture(true)      // Enable at runtime
ctx.set_mouse_capture(false)     // Disable at runtime
ctx.toggle_mouse_capture()       // Toggle, returns new state

Context Methods for Inline Mode

rust
ctx.is_inline()                    // true if running in inline mode
ctx.append_transcript_lines(lines) // Append styled lines above the viewport
ctx.append_transcript_element(el)  // Append a rendered Element to history

Transcript / Native Scroll Pattern

Inline transcript mode allows modeling a Claude Code or Gemini CLI style interaction:

  • Keep a small live viewport for the composer and any in-progress response.
  • Call ctx.append_transcript_element(element) when a user/assistant message is complete.
  • The appended element is rendered once and inserted into terminal history above the viewport.
  • append_transcript_element is for already-expanded widget trees. Do not pass Component elements or subtrees that still contain them.
  • Use Text::overflow(Overflow::Wrap) (or Overflow::Auto with a width constraint) inside appended subtrees so lines wrap to the current terminal width instead of clipping.
rust
fn update(&mut self, msg: Msg, ctx: &mut Context<Self>) -> Update {
    match msg {
        Msg::UserSubmitted(prompt) => {
            ctx.append_transcript_element(message_card("You", &prompt));
            Update::full()
        }
        Msg::AssistantFinished(text) => {
            ctx.append_transcript_element(message_card("Assistant", &text));
            Update::full()
        }
    }
}

Resize Behavior

Inline resize behavior is defined by surface mode semantics, not by user-facing terminal wrap controls.

  • Ephemeral mode keeps a fixed inline viewport, disables terminal autowrap, and preserves the current live session during resize.
  • Transcript mode resets to a full-height inline surface on resize, clears the visible terminal, and redraws the app from scratch while leaving prior output in native scrollback.

The last terminal column is reserved only for ephemeral inline mode, where autowrap stays disabled during resize.

Limits in Inline Mode

  • Root-level overlays suppressed: Modal, Popover, and Toast portals are disabled to avoid terminal history corruption. Use local overlays or inline widgets instead.
  • Image rendering disabled: Falls back to text placeholders.
  • Last column reserved: Avoids width-change soft-wrap artifacts.
  • Mouse coordinates: Translated from terminal-space to viewport-space before hit testing.

Examples

  • examples/inline.rs - Basic inline ephemeral session
  • examples/inline_auto_height.rs - Content-sized viewport with InlineHeight::auto()
  • examples/native_scroll_chat.rs - Transcript-style native scrollback chat
  • examples/inline_list_picker.rs - Inline lists, filtering, and activation
  • examples/inline_choices.rs - Choice selection in inline mode

MPL-2.0