Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Layout and chrome

Structural widgets: split panes, scroll views, scrollbars, panels, collapsible sections, and header/footer chrome.

SplitPane

SplitPane splits an area into two resizable regions with a draggable divider. Horizontal splits stack vertically, vertical splits sit side by side.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;
use tuile::widgets::SplitSize;

fn demo(area: Rect, buf: &mut Buffer) {
let mut state = SplitState::new(SplitSize::Ratio(0.3), 10, 10);
let (left, right) = SplitPane::new()
    .direction(Direction::Horizontal)
    .render_split(area, buf, &mut state);
}
fn main() {}

SplitState owns initial (the size spec at creation), pos (current first-pane size in cells), min_first, min_second, and the cached layout. render_split returns (first_rect, second_rect).

MethodEffect
direction(Direction)Horizontal (side by side) or Vertical (stacked)
divider(SplitDivider)Line (default), Thick, Dotted, Hidden

Keys and mouse: Drag the divider to resize. Double-click the divider to reset to initial. Call state.resize_by(delta) from your key handler to resize by delta cells. state.collapse_first(), state.collapse_second(), and state.restore() hide and show panes.

When not to use: Single fixed split with no interaction: use ratatui_core::layout directly. Three-way split: nest two SplitPane calls or use layout::stack.

ScrollView

ScrollView renders content into an offscreen buffer larger than the viewport, then blits the visible window. Wheel, arrow keys, PageUp/PageDown, Home/End scroll. Optional smooth tweened scrolling.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;
use tuile::draw::put;

fn demo(area: Rect, buf: &mut Buffer) {
let mut state = ScrollViewState::new();
ScrollView::new()
    .content_size(200, 100)
    .smooth(true)
    .render_with(area, buf, &mut state, |cbuf, carea| {
        put(cbuf, 0, 0, "Content...", carea.width, Style::new());
    });
}
fn main() {}

ScrollViewState tracks offset_x, offset_y, anim_y (the vertical scroll tween), content (width, height), viewport, and vbar/hbar (scrollbar states). Call state.scroll_to(x, y, now, dur) or state.scroll_by(dx, dy, now, dur) to scroll programmatically. A zero duration snaps; a non-zero duration animates.

MethodEffect
content_size(w, h)Offscreen buffer dimensions in cells
smooth(bool)Tween vertical scroll (default false)
scrollbars(ScrollBars)Auto (default), Always, Never

When you need it: Content larger than the viewport (a 200-line log in a 20-row area, a wide table). When you want smooth animated scrolling. When you need scrollbars.

When you do not: The content fits. You are already paginating in your data model (a TUI table with a virtual scroll). Rendering into a temporary buffer costs allocation; if you can clip with ratatui_core::layout instead, do.

Scrollbar

Scrollbar is the reference implementation of the tuile widget contract. It uses eighth-block glyphs for the thumb ends (Textual style), responds to wheel and drag, and updates state.offset.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;

fn demo(area: Rect, buf: &mut Buffer) {
let mut state = ScrollbarState::new();
let (total_rows, visible_rows) = (200, 10);
Scrollbar::vertical(total_rows, visible_rows)
    .offset(40)
    .render(Rect::new(39, 0, 1, 10), buf, &mut state);
}
fn main() {}

ScrollbarState tracks offset, content, viewport, axis, and drag state. state.handle_mouse(m) moves offset on wheel or drag and returns Outcome::Changed when it moved.

MethodEffect
vertical(content, viewport)Vertical scrollbar (axis defaults Vertical)
horizontal(content, viewport)Horizontal scrollbar
offset(usize)Current scroll position
hide_when_fits(bool)Default true: invisible when content <= viewport

The source shape: Scrollbar is simple enough to show in full. The widget is a builder holding content, viewport, offset, axis, hide_when_fits, and a theme. impl StatefulWidget for Scrollbar paints the track and thumb. ScrollbarState implements Interactive and turns mouse events into offset changes. This pattern repeats across tuile: builder, render, state, interactive.

Panel

Panel draws a bordered container with optional title, subtitle, padding, shadow, and background fill. Returns the inner content rect.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;
use tuile::draw::Border;

fn demo(area: Rect, buf: &mut Buffer) {
let inner = Panel::new()
    .title("Settings")
    .border(Border::Round)
    .shadow(true)
    .render(area, buf);
}
fn main() {}

Panel has no state. It is a Widget-like builder that returns a Rect instead of implementing Widget.

MethodEffect
title(&str)Top-left title
title_align(Alignment)Where the title sits
title_right(&str)A second title on the right of the top edge
subtitle(&str)Bottom-left subtitle
footer(&[(&str, &str)])Key and description pairs along the bottom edge
border(Border)Any Border style: Round, Thick, Double, Heavy, Tall, Panel, Wide, Inner, Outer, Dashed, Solid, Ascii, Hkey, Vkey, Blank, None
border_color(Rgb)Override the border colour
background(PanelBg)Transparent, Surface, Panel, Boost
padding(u16)Inner padding
shadow(bool)Drop shadow
badge(&str)A badge in the title row
variant(Variant)Title colour
focused(bool)Use the focused border colour

Panel::card() presets a surface background, a round border and a shadow. Panel::section() presets no border, for a title row with a rule instead of a box.

Placeholder

Placeholder is Textual’s placeholder block: cycles colours, shows dimensions and a label. Use it to sketch layouts.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;

fn demo(area: Rect, buf: &mut Buffer) {
Placeholder::new()
    .index(0)
    .render(area, buf, "Sidebar");
}
fn main() {}

index picks a colour from a 22-colour palette. variant(PlaceholderVariant) overrides the display: Default (show name), Size (show WxH), Text (show custom text). The name is passed to render(), not a builder method.

Collapsible and Accordion

Collapsible is a header row that expands to reveal content. Click the header or call state.toggle() to open and close. The height animates.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;
use tuile::draw::put;

fn demo(area: Rect, buf: &mut Buffer) {
let mut state = CollapsibleState::new(false);
let used = Collapsible::new()
    .title("Details")
    .render_with(area, buf, &mut state, 10, |inner, buf| {
        put(buf, inner.x, inner.y, "Content", inner.width, Style::new());
    });
}
fn main() {}

CollapsibleState::new(open) sets the initial state. state.open is public. state.animating(now) is true while the height tween is active.

MethodEffect
title(&str)Header text
subtitle(&str)Secondary header text
header_style(CollapsibleHeader)Plain, Panel (bordered), Underline
markers(closed, open)The two glyphs shown before the title
border(Border)Frame around the whole widget
duration(Duration)Expand and collapse time
focused(bool)Focus styling

render_with takes content_height (the fully expanded height) and a closure that draws into the content area.

Accordion stacks multiple collapsibles. Set exclusive(true) to enforce single-open mode.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;
use tuile::draw::put;

fn demo(area: Rect, buf: &mut Buffer) {
let mut state = AccordionState::new(3, true);
Accordion::new()
    .titles(&["General", "Privacy", "Advanced"])
    .render_with(area, buf, &mut state, &[5, 5, 5], |idx, inner, buf| {
        put(buf, inner.x, inner.y, &format!("Section {}", idx), inner.width, Style::new());
    });
}
fn main() {}

AccordionState::new(count, exclusive) creates count collapsible sections. state.states is public (a Vec<CollapsibleState>). The render closure receives (index, area, buf). Pass heights as a slice of content heights per section.

AppHeader

Fixed-height application header with icon, title, subtitle, clock, and clickable action labels.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;

fn demo(area: Rect, buf: &mut Buffer) {
let mut state = AppHeaderState::default();
AppHeader::new()
    .title("Settings")
    .subtitle("Preferences")
    .icon("⊛")
    .clock(true)
    .actions(&["Help", "Quit"])
    .render(area, buf, &mut state);
if let Some(idx) = state.take_action() {
    // handle action click
}
if state.take_icon_click() {
    // handle icon click
}
}
fn main() {}

AppHeaderState tracks hover and pressed actions. state.take_action() returns Some(index) when an action is clicked. state.take_icon_click() returns true when the icon is clicked and clears the flag.

MethodEffect
title(&str)Main title (left, after icon)
subtitle(&str)Subtitle (left, below title if multi-row header)
icon(&str)Left icon (clickable)
clock(bool)Right-aligned HH:MM:SS clock
actions(&[&str])Right-aligned clickable labels (left of clock)

KeyFooter

Horizontal footer that renders key bindings as [key] description pairs. Truncates by priority when narrow.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;

fn demo(area: Rect, buf: &mut Buffer) {
let mut state = KeyFooterState::default();
KeyFooter::new()
    .bindings(&[("q", "Quit"), ("^S", "Save"), ("?", "Help")])
    .render(area, buf, &mut state);
if let Some(idx) = state.take_pressed() {
    // handle binding click
}
}
fn main() {}

FooterBinding::new(key, desc) creates a binding. Call .priority(n) to set sort order (lower renders first, higher survives truncation). Call .disabled() to grey out a binding.

state.take_pressed() returns Some(index) when a binding is clicked.

StatusLine

One-row status strip of separated segments with optional right-aligned text. Each segment has an optional icon, text, and colour.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;
use tuile::widgets::{StatusSegment, StatusSep};
use tuile::theme::Rgb;

fn demo(area: Rect, buf: &mut Buffer) {
StatusLine::new(&[
    StatusSegment::new("π"),
    StatusSegment::new("Opus 5").icon("◕").color(Rgb(255, 140, 0)),
    StatusSegment::new("/tmp").icon("⌂"),
    StatusSegment::new("4.0%/1M"),
])
.sep(StatusSep::Chevron)
.right("omp")
.render(area, buf);
}
fn main() {}
MethodEffect
sep(StatusSep)Chevron (›, default), Dot, Pipe, Arrow, Space
right(&str)Right-aligned text (colour defaults th.accent)
right_color(Rgb)Override right text colour
bg(Rgb)Background strip colour (default th.background)

StatusLine is stateless and implements Widget.

Steps

Horizontal or vertical step indicator for process flows. Shows step labels, status (pending, active, complete, error), and connecting lines.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;

fn demo(area: Rect, buf: &mut Buffer) {
let mut state = StepsState::default();
Steps::new(&["Start", "Build", "Deploy"])
    .active(1)
    .render(area, buf, &mut state);
}
fn main() {}
MethodEffect
active(usize)Current step index (drawn as active)
statuses(&[StepStatus])Per-step status: Pending, Active, Done, Error, Skipped
direction(Direction)Horizontal (default) or Vertical

StepsState is currently a placeholder (the widget has no interaction). Steps also provides a Timeline widget for vertical time-stamped entries.

Layout helpers

The tuile::layout module provides small helpers on top of ratatui_core::layout.

Centring:

extern crate tuile;
extern crate ratatui_core;
use tuile::layout::{center, center_h};
use tuile::ratatui_core::layout::Rect;

fn demo(area: Rect) {
let popup = center(area, 60, 20);
let banner = center_h(area, 80);
}
fn main() {}

center(area, w, h) returns a w×h rect centred in area (clamped to fit). center_h(area, w) centres horizontally only, full height.

Padding:

extern crate tuile;
extern crate ratatui_core;
use tuile::layout::{pad, pad_trbl};
use tuile::ratatui_core::layout::Rect;

fn demo(area: Rect) {
let inner = pad(area, 2, 1);
let inner2 = pad_trbl(area, 1, 2, 1, 2);
}
fn main() {}

pad(area, x, y) shrinks by x cells left/right and y cells top/bottom. pad_trbl(area, t, r, b, l) takes individual sides (CSS order).

Rows and columns:

extern crate tuile;
extern crate ratatui_core;
use tuile::layout::{stack, columns};
use tuile::ratatui_core::layout::Rect;

fn demo(area: Rect) {
let rows = stack(area, &[3, 1, 5], 1);
let cols = columns(area, 3, 2);
}
fn main() {}

stack(area, heights, gap) splits into fixed-height rows with gap between them; the last row takes the remainder. columns(area, n, gap) splits into n equal columns with gap between them.

Popup placement:

extern crate tuile;
extern crate ratatui_core;
use tuile::layout::popup_below;
use tuile::ratatui_core::layout::Rect;

fn demo(anchor: Rect, bounds: Rect) {
let popup = popup_below(anchor, 40, 10, bounds);
}
fn main() {}

popup_below(anchor, w, h, bounds) places a w×h popup below anchor, flipping above when it would overflow bounds, and sliding horizontally to stay inside.