Skip to content

WrappedText

WrappedText renders long plain-text prose. It first hard-wraps text to a specified width, then writes the measured line count back to layout height so an outer ScrollView can calculate the correct scroll range.

wrapped text demo

This recording is generated by docs/tapes/wrapped-text.tape. The example puts a Chinese/English explanatory text inside ScrollView: press j/k to scroll line by line, PageDown/PageUp to page, and the bottom title shows the current offset.

cargo run --example wrapped_text

Press j/k or arrow keys to scroll line by line, PageDown/PageUp to page, Home/End to jump to the top or bottom, and q to quit.

Text(wrap: true) fits short sentences, labels, hints, and rich text fragments. It soft-wraps during draw, but layout still has a hard time knowing how many lines it will occupy.

Long prose is different. Readers, log details, help docs, and explanation panels all need stable content height; otherwise scrollbars, End, PageDown, and external state panels become inaccurate.

WrappedText hard-wraps before layout, so ScrollView receives the real content height.
ScrollView(
    flex_direction: Direction::Vertical,
    scroll_view_state: scroll_state,
    scroll_bars: ScrollBars {
        vertical_scrollbar_visibility: ScrollbarVisibility::Always,
        horizontal_scrollbar_visibility: ScrollbarVisibility::Never,
        ..Default::default()
    },
    block: Block::bordered()
        .title(Line::from(" measured prose ").cyan().centered()),
) {
    WrappedText(
        text: BODY,
        wrap_width: 72,
        style: Style::new().white(),
    )
}

text is plain text. The component does not include sections, search hits, highlighting, or reading progress; compose those at the page level.

wrap_width is the wrap width in terminal columns. When long prose is placed inside ScrollView, prefer passing it explicitly:

WrappedText(
    text: body,
    wrap_width: 72,
)

The current component update stage does not yet know the final draw width, so an explicit width lets WrappedText compute an accurate line count before layout.

If wrap_width is not passed, the component tries to infer it from width: Constraint::Length(...). If neither is available, it uses a conservative default width. That is fine for temporary previews, but not as a final form inside a scroll container.

auto_height is enabled by default. When disabled, the component still hard-wraps text, but it will not automatically set height to the line count. This is useful for special cases where you intentionally want fixed-height truncation:

WrappedText(
    text: body,
    wrap_width: 72,
    auto_height: false,
    height: Constraint::Length(8),
)

WrappedText is responsible only for measuring prose. Scroll keys, scrollbars, and external offset are still handled by ScrollView or page handlers:

hooks.use_event_handler(EventScope::Current, EventPriority::Normal, move |event| {
    let Event::Key(key) = &event else {
        scroll_state.write().handle_event(&event);
        return EventResult::Ignored;
    };

    match key.code {
        KeyCode::Char('j') | KeyCode::Down | KeyCode::PageDown => {
            scroll_state.write().handle_event(&event);
            EventResult::Consumed
        }
        KeyCode::Char('k') | KeyCode::Up | KeyCode::PageUp => {
            scroll_state.write().handle_event(&event);
            EventResult::Consumed
        }
        _ => EventResult::Ignored,
    }
});

If you need per-line highlighting, selection, virtualization, or index-based jumps, see VirtualList. For short text display, keep using Text.