Skip to content

Layout and Text Primitives

This page covers the most foundational component group. They do not own business state or register keyboard events; they only turn the component tree into predictable terminal regions and text output.

If you know React, think of View as the most common layout container and Fragment as a wrapper that does not produce an extra layout node. The difference is that ratatui-kit layout eventually lands on ratatui Layout, Constraint, Rect, and Buffer, so sizes and region boundaries are more explicit than in Web UI.

What you want to doComponentNotes
Organize children horizontally or verticallyViewDefault container; supports flex_direction, gap, width, height, margin, and offset
Add a border, title, and padding around contentBorderDraws its own Block; child regions enter Block::inner
Center content inside a regionCenterTransparent wrapper implemented with three nested Views
Overlay content at absolute coordinatesPositionedDoes not participate in ordinary flex sizing; can render Clear
Return multiple child nodes without adding a layout layerFragmentTransparent layout; inherits the first child’s layout
Render short text, Line, Text, or ParagraphTextSuitable for fixed-region text; use WrappedText for auto-height prose

View is the ordinary layout container. It has no drawing content of its own; it passes children to the default flex layout algorithm to compute their regions.

element!(
    View(
        flex_direction: Direction::Vertical,
        gap: 1,
        margin: Margin::new(1, 1),
    ) {
        Text(text: "header")
        View(height: Constraint::Fill(1)) {
            Text(text: "body")
        }
        Text(text: "footer")
    }
)

View layout fields come from #[with_layout_style]:

FieldMeaning
flex_directionDirection used to split child regions; defaults to ratatui Direction::Vertical
justify_contentMaps to ratatui Flex
gapSpacing between child regions
width / heightConstraint used by parent layout when computing the current component region
margin / offsetApplied to the current region before drawing the component and subtree

If you are unsure which container to use, start with View. It is the closest thing to a plain “layout node”.

Border does two things: it participates in parent layout and renders a ratatui Block during draw. After rendering the border, children are drawn inside Block::inner(drawer.area).

element!(
    Border(
        width: Constraint::Length(42),
        height: Constraint::Length(9),
        padding: Padding::new(1, 1, 0, 0),
        border_style: Style::new().fg(Color::Cyan),
        top_title: Line::from(" settings ").centered(),
        bottom_title: Line::from(" q quit ").centered(),
    ) {
        Text(text: "content")
    }
)

Common props:

FieldMeaning
paddingInner padding reserved before entering Block::inner
border_styleBorder style
bordersWhich edges to show; defaults to Borders::ALL
border_setBorder character set
styleBase style for the whole block
top_title / bottom_titleTop and bottom titles; you can pass Line directly

Border is a content-grouping component. It should not be used as decorative page-section chrome. In terminal UI, borders consume real character cells, and excessive nesting reduces content density.

Center is a transparent layout helper. It has no draw behavior of its own; it puts children into internally nested Views and centers the specified width and height inside the parent region.

element!(
    Center(
        width: Constraint::Length(48),
        height: Constraint::Length(9),
    ) {
        Border {
            Text(text: "centered panel")
        }
    }
)

Center fits splash screens, empty states, small modal content, and focused example regions. For complex responsive layout, composing Views directly is usually clearer.

Positioned draws a subtree at an absolute Rect:

element!(
    Positioned(
        x: 4,
        y: 2,
        width: 40,
        height: 8,
        clear: true,
    ) {
        Border {
            Text(text: "floating panel")
        }
    }
)

Its layout size is set to Constraint::Length(0), so it does not occupy parent flex space. During draw, it changes drawer.area to Rect::new(x, y, width, height); when clear: true, it renders ratatui Clear first, which is useful for overlaying background content.

Use Positioned carefully. It bypasses the ordinary layout flow and is well suited to local floating layers, cursor-adjacent hints, or debug overlays, but not to the main page structure. Main structure should still be expressed with View, Border, ScrollView, or dedicated components.

Fragment is a transparent container for returning multiple child nodes:

element!(
    Fragment {
        Text(text: "line one")
        Text(text: "line two")
    }
)

It calls set_transparent_layout(true), so it does not create its own independent layout node. At runtime, it inherits the first child component’s LayoutStyle; if there are no children in the current frame, it resets to the default layout.

This matches the transparent layout rule for function components: do not expect setting width / height on the outside of a transparent wrapper to make it occupy space by itself. Put layout properties on the root child component it returns.

Text is a thin wrapper around Paragraph. It can receive a string, Line, ratatui Text, or Paragraph directly:

element!(
    Text(
        text: Line::styled("ready", Style::new().fg(Color::Green)),
        alignment: Alignment::Center,
    )
)

Common props:

FieldMeaning
text&str, String, Line, ratatui Text, or Paragraph
styleOverall style applied to the Paragraph
alignmentText alignment
scrollParagraph scroll offset, typed as Position
wrapWhether to enable Wrap { trim }; passing true trims whitespace

Text.wrap only affects how the Paragraph wraps inside the current region; it does not feed the measured wrapped height back to parent layout. For “long prose that should compute its own height, then scroll inside ScrollView”, use WrappedText.

You can also bridge ratatui widgets directly inside element!:

element!(
    Border {
        widget(paragraph)
        stateful(list, list_state)
    }
)

widget(...) is for stateless widgets; stateful(...) is for widgets like List and Table that need ratatui state. They are low-level escape hatches. Simple bridges can use them directly; if you need to wrap a complex widget as a reusable component, see Native Widget Bridging.

  • Use View / Border / Center for layout structure.
  • Use WrappedText for long prose and ScrollView for scrollable regions.
  • Do not handwrite a full input-exclusivity stack for modals with Positioned; prefer Modal or the packaged modal components.
  • Use widget / stateful only when a native widget needs to be bridged; write a Component when you need state, events, layout, and reuse.

For more runtime-level explanation, see Component Model and Render Loop.