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:
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'scursoraccent. - Hovered (
contains_hover): border takesprimary— 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
| Method | Description |
|---|---|
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:
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.