Skip to content

Panel

PanelWidget is the themed container: a bordered surface that groups content, with optional top/bottom titles and focus/hover accents on the border. It is paint-only — a panel has no behavior, so there is no interactive half.

Panels and Views

Grouping and focus belong to View; a panel is the paint of a region. The typical wiring draws a panel around a view's area and passes the signals the view already has — view.contains_focus() lights the border while focus is anywhere inside the pane:

rust
use ratcn::PanelWidget;

View::new("inbox")
    .child("list", list)
    .content(|frame, _state, view| {
        let panel = PanelWidget::new()
            .themed(view.theme())
            .title(" Inbox ")
            .focused(view.contains_focus());
        let inner = panel.inner(view.area());
        frame.render_widget(panel, view.area());
        view.render_child(frame, "list", inner);
    });

The landing demo's tiles and the views demo's panes are both drawn this way.

Interaction accents

The themed style resolves the border from the two container signals, in one place:

  • Focused (contains_focus): border takes the theme's cursor accent.
  • Hovered (contains_hover): border takes primary — the softer accent, so the two states read differently. Focus wins when both apply.

Both signals are app-provided booleans; wire them from RenderCtx / ViewCtx or from your own state.

PanelWidget API

MethodDescription
PanelWidget::new()Creates a paint-only panel.
.themed(&theme)Applies theme colors and the theme's border set.
.style(PanelStyle)Uses an explicit style.
.title(...)Title in the top border.
.title_bottom(...)Title in the bottom border.
.title_alignment(...)Title alignment (default centered).
.padding(Padding)Inner padding between border and content.
.focused(bool)Focus accent on the border (wire to contains_focus).
.hovered(bool)Hover accent on the border (wire to contains_hover). Focus wins when both are true.
.inner(area)The content area inside border and padding, for laying out children.

Beyond the panel

PanelWidget is deliberately small: theme, states, titles, padding. For any Ratatui Block option it does not surface, build a Block directly — theme.border_style.to_border_set() bridges the theme's border choice:

rust
use ratatui::widgets::Block;

let block = Block::bordered()
    .border_set(theme.border_style.to_border_set())
    .border_style(Style::default().fg(theme.border));

That is also exactly what the library's own components do for their borders (Input/TextArea titles, dialog chrome, toasts) — components never depend on a sibling component.

Component Borders

Input and TextArea draw their own titled borders with .title(...). Prefer that API for those components so invalid styling and inner sizing remain consistent. Use PanelWidget for surrounding panes, tiles, and custom static sections.