# tuika component gallery
A visual catalog of tuika's components — each with a name, a one-line
description, and an animated demo.
## Motion
Animated from a host-supplied frame counter (see the [`anim`](https://docs.rs/tuika/latest/tuika/anim/index.html) module).
### `Spinner`
A frame-cycled activity glyph — `Braille` (smooth default), `Line` (ASCII
fallback), or `Dots`. [API](https://docs.rs/tuika/latest/tuika/components/struct.Spinner.html)
<img src="demos/spinner.gif" width="880" alt="Spinner demo">
```rust
use tuika::prelude::*;
view! {
row(gap = 1) {
node(Spinner::new(frame).style(SpinnerStyle::Braille))
text("working…")
}
}
```
### `ProgressBar`
A single-row bar: determinate (sub-cell eighth-block fill, optional `NN%`) or an
indeterminate marquee driven by the frame counter. `.label("…")` overlays a
centered caption and clips it on narrow terminals.
[API](https://docs.rs/tuika/latest/tuika/components/struct.ProgressBar.html)
<img src="demos/progress_bar.gif" width="880" alt="ProgressBar demo">
```rust
use tuika::prelude::*;
view! {
col(gap = 1) {
node(ProgressBar::determinate(0.6).label("0:42/3:07").percent(true))
node(ProgressBar::indeterminate(frame))
}
}
```
### `ActivityList`
A vertical lifecycle view for multi-step work: queued, running, succeeded,
failed, or skipped. An item may compose a determinate progress bar beneath its
status row. Use `ActivityList` to answer *which step is in which state*; use a
standalone `ProgressBar` to answer *how much of one measurable operation is
complete*. The host still owns the task model and scheduling.
[API](https://docs.rs/tuika/latest/tuika/components/struct.ActivityList.html)
<img src="demos/activity_list.gif" width="880" alt="ActivityList demo">
```rust
use tuika::prelude::*;
let tasks = vec![
ActivityItem::new("Resolve dependencies", ActivityStatus::Succeeded),
ActivityItem::new("Compile", ActivityStatus::Running).progress(0.42),
ActivityItem::new("Test", ActivityStatus::Queued),
];
view! { node(ActivityList::new(tasks).frame(frame).gap(1)) }
```
### `Loader`
A spinner, a message, and an optional trailing hint on one row.
[API](https://docs.rs/tuika/latest/tuika/components/struct.Loader.html)
<img src="demos/loader.gif" width="880" alt="Loader demo">
```rust
use tuika::prelude::*;
view! {
node(Loader::new(frame, "compiling crate…").hint("esc to cancel"))
}
```
### `Timeline`
A scheduler-free keyframe track: values eased over frame offsets, with
`Once`/`Loop`/`PingPong` repeat, sampled purely from the host frame counter — the
minimal analog of OpenTUI's Timeline. Compose several (one per animated property)
rather than reconciling a tween tree. The demo drives three `ProgressBar`s from
three timelines.
[API](https://docs.rs/tuika/latest/tuika/anim/struct.Timeline.html)
<img src="demos/timeline.gif" width="880" alt="Timeline demo">
```rust
use tuika::anim::ease_out;
use tuika::prelude::*;
let slide = Timeline::new().keyframe(0, 0.0).ease(30, 1.0, ease_out);
let pulse = Timeline::new()
.keyframe(0, 0.0).keyframe(10, 1.0).keyframe(20, 0.0)
.repeat(Repeat::Loop);
let x = slide.sample(frame); // 0.0 → 1.0 over 30 frames, then holds
```
## Text
### `Text`
A block of pre-styled [`Line`](https://docs.rs/ratatui)s drawn top-down and
clipped. `Paragraph` word-wraps plain text in one style; `Wrap` word-wraps
pre-styled lines while preserving per-span styles.
[API](https://docs.rs/tuika/latest/tuika/components/text/struct.Text.html)
Horizontal alignment is honored. `Text` and `Wrap` read each `Line`'s
`alignment` (unset = flush-left), so centered titles, right-aligned totals, and
centered empty-state messages built by an existing formatting layer render as
intended; `Wrap` carries a line's alignment onto every reflowed row.
`Paragraph` takes one alignment for the whole block via `.alignment(..)`.
<img src="demos/text.png" width="880" alt="Text demo">
```rust
use ratatui::layout::Alignment;
use ratatui::text::Line;
use tuika::prelude::*;
view! {
col(gap = 1) {
// Per-line alignment on pre-styled lines.
node(Text::new(vec![
Line::from("flush left"),
Line::from("centered").centered(),
Line::from("flush right").right_aligned(),
]))
// One alignment for a wrapped plain-text block.
node(Paragraph::new("word-wrapped prose", style).alignment(Alignment::Center))
}
}
```
### `Rule`
A one-row horizontal separator: optional leading title, then a fill glyph out to
the width. [API](https://docs.rs/tuika/latest/tuika/components/struct.Rule.html)
<img src="demos/rule.png" width="880" alt="Rule demo">
```rust
use tuika::prelude::*;
view! {
node(Rule::new().title(" Section "))
}
```
## Markdown & code
### `Markdown` + `MarkdownState`
Renders CommonMark (plus GFM tables and strikethrough) to styled lines —
word-wrapping prose and fitting code and tables to the render width.
`MarkdownState` adds incremental rendering for streaming text. See the
[markdown guide](markdown.md) for streaming, tables, fenced-block renderers,
highlighting, links, and images.
[API](https://docs.rs/tuika/latest/tuika/components/markdown/index.html)
<img src="demos/markdown.gif" width="880" alt="Markdown streaming demo">
The presentational inline HTML tags render too — `<b>`, `<em>`, `<code>`,
`<kbd>`, `<mark>`, `<a>`, `<br>`, `<sub>`/`<sup>` — each resolving the same
`StyleSheet` role as the markdown it mirrors. Block-level HTML is a seam; see
[Inline HTML](markdown.md#inline-html).
<img src="demos/markdown_html.png" width="880" alt="Inline HTML in markdown: strong, emphasis, struck and underlined text, a highlighted run, keyboard keys, a link, Unicode subscript and superscript, and a line broken by a br tag">
### `Html`
Renders an HTML fragment to styled lines: headings, paragraphs, lists,
definition lists, block quotes, `<pre>`, `<hr>`, `<table>`,
`<details>`/`<summary>`, and the presentational inline elements. Every element
resolves a `StyleSheet` role, so HTML inherits the app's theme like everything
else. No CSS — this renders content, not pages. Ships in the companion crate
[`tuika-html`](../crates/tuika-html/), which also supplies the
`MarkdownBlockRenderer` that lays out HTML blocks inside
[Markdown](markdown.md#block-html).
[API](https://docs.rs/tuika-html/latest/tuika_html/struct.Html.html)
<img src="../crates/tuika-html/examples/html_view/html_view.png" width="880" alt="The Html view filling a bordered pane: a heading, wrapped prose with bold and italic runs, a definition list, a box-drawn table, a block quote, a pre block on a code background, a rule, and a footer line with a link, keyboard keys and a highlighted run">
### `CodeBlock`
A themed, syntax-highlighted fenced block: a language label, a left rail, and a
code background. Highlighting comes from a pluggable `Highlighter` (none → plain,
theme-colored text); the `tuika-codeformatters` crate ships a tree-sitter one. An
optional line-number gutter (`line_numbers(true)` / `start_line(n)`) rides to the
left of the rail.
[API](https://docs.rs/tuika/latest/tuika/components/struct.CodeBlock.html)
<img src="demos/code_block.png" width="880" alt="CodeBlock demo">
```rust
use tuika::prelude::*;
view! {
node(CodeBlock::new("rust", "fn main() {}").highlighter(&highlighter).line_numbers(true))
}
```
### `Diff`
A line-oriented diff (LCS) rendered **unified** (`+`/`-`/` ` gutters) or
**side-by-side**, with an optional line-number gutter. Base, row, gutter, and
divider colors resolve from semantic styling; `DiffStyle` is the per-instance
override. The pure `diff::rows(old, new)` classifier is reusable on its own.
[API](https://docs.rs/tuika/latest/tuika/components/diff/struct.Diff.html)
<img src="demos/diff.png" width="880" alt="Diff demo">
```rust
use tuika::prelude::*;
view! {
node(Diff::new(old, new).mode(DiffMode::SideBySide).line_numbers(true))
}
```
## Layout
See the [layout guide](layout.md) for wrapping, grow/shrink, line alignment,
measurement requests, migration notes, and choosing Flex, Flow, or Grid.
### `AppShell`
A compact application frame for tool-style TUIs: intrinsic header and status
regions, optional theme-aware rules, one growing main view, and a footer that
fits `KeyHints` or any custom view. Every region is optional except main;
`before_main` and `after_main` accept borrowed views and preserve call order
when an application needs different chrome. On short screens rules and status
collapse before the one-row main/footer minimums; width-sensitive children
receive the terminal's actual width.
[API](https://docs.rs/tuika/latest/tuika/components/struct.AppShell.html)
<img src="demos/app_shell.png" width="880" alt="AppShell with header, file-list content, status, rules, and responsive key hints">
```rust
use tuika::prelude::*;
let screen = AppShell::new(content)
.header(Text::raw("my tool"))
.top_rule()
.status(StatusBar::new().left(status_spans))
.bottom_rule()
.footer(KeyHints::from_keymap(&keymap));
```
### `SelectionScreen`
A responsive full-screen picker for the repeated action, agent, permission,
and resume shape: optional leading rule, heading-styled header, separator,
selectable body, optional trailing rule, and a `KeyHints` footer. It composes
`AppShell`, the same row renderer as `SelectList`, `SelectState`, and semantic
theme roles. The body automatically windows to its allocated height, keeping
the current selection visible on short terminals. `borrowed` reuses a host row
slice without cloning; `windowed` accepts only a host-supplied `VirtualWindow`;
`new` owns rows. Header and footer builders accept custom
owned or frame-borrowed views, and per-instance header/selection styles remain
available without embedding an application palette.
[API](https://docs.rs/tuika/latest/tuika/components/struct.SelectionScreen.html)
<img src="demos/selection_screen.png" width="880" alt="Responsive SelectionScreen action picker with header, virtualized rows, rules, and key hints">
```rust
use tuika::prelude::*;
let screen = SelectionScreen::borrowed("Select an action", &rows, &state)
.leading_rule()
.trailing_rule()
.footer(KeyHints::from_keymap(&keymap));
```
The compilable [AGF-shaped example](https://github.com/everruns/tuika/blob/main/examples/selection_screen.rs)
measures the caller expression exactly: **8 nonblank LOC before, 4 after**.
The before form clones the row vector into `SelectList`; the after form borrows
it and derives virtualization from the allocated body height.
### `Flex`
The flexbox container and composition primitive — `grow(n)` children share
leftover space by weight, `fixed(n)` reserve exact size, with `gap` and
`padding`. It *is* the `view!` DSL's `row`/`col`. `element` and `view!` preserve
frame borrows through nested Flex and Boxed containers as `ScopedElement<'_>`;
owned trees continue to use `Element` without lifetime annotations.
[API](https://docs.rs/tuika/latest/tuika/components/struct.Flex.html)
<img src="demos/flex.png" width="880" alt="Flex demo">
```rust
use tuika::prelude::*;
view! {
row(gap = 1) {
grow(1) { node(left) }
fixed(12) { node(right) }
}
}
```
Need the child rects *before* (or without) painting — to size a scroll region
to a pane's real height, hit-test a click, or decide what fits? `Flex::solve`
runs the same measure-then-solve pass render uses and returns one `Rect` per
child, painting nothing. Padded containers measure children against the inner
box, and a `Flex` measured as an `Auto` child honors its own fixed and percent
dimensions. The underlying flexbox solver is also callable directly as
`tuika::layout::solve(area, &style, &items)` for layouts built without a `Flex`.
```rust
use tuika::prelude::*;
use ratatui::layout::Rect;
let flex = Flex::row()
.fixed(8, element(Text::raw("sidebar")))
.grow(1, element(Text::raw("content")));
let theme = Theme::default();
let ctx = RenderCtx::new(&theme);
let rects = flex.solve(Rect::new(0, 0, 40, 10), &ctx); // [sidebar_rect, content_rect]
```
`FlexItemStyle` separates child-owned basis/grow/shrink/min/max/`align_self`
from container-owned direction, wrapping, gaps, justification, and line
alignment. `Flex::wrap(FlexWrap::Wrap)` forms flex lines; positive and negative
free space are distributed by weight with exact cell-boundary rounding.
### `Flow`
A row-oriented wrapping flex container for tags, actions, and other items whose
intrinsic widths decide the line breaks.
[API](https://docs.rs/tuika/latest/tuika/components/struct.Flow.html)
<img src="demos/flow.png" width="880" alt="Flow demo">
```rust
use tuika::prelude::*;
let flow = Flow::new()
.gap(1)
.item(element(Text::raw("build")))
.item(element(Text::raw("release-ready")));
```
### `Grid`
A deliberately small equal-column, row-major terminal grid with intrinsic row
heights, independent gaps, padding, and exact boundary rounding. It omits CSS
Grid's named lines, implicit tracks, spanning, and dense packing.
[API](https://docs.rs/tuika/latest/tuika/components/struct.Grid.html)
<img src="demos/grid.png" width="880" alt="Grid demo">
```rust
use tuika::prelude::*;
let grid = Grid::new(3)
.gap(1)
.cell(element(Text::raw("one")))
.cell(element(Text::raw("two")));
```
### `Boxed`
A border + padding + title wrapping one child. The border color is focus-aware
by default (theme `border` / `border_focused`); `border_color(Color)` overrides
that with an explicit color for semantic frames — an accent or danger modal, or
a per-pane color a host resolves itself. An optional `title_bottom` rides the
bottom border — the slot for a `1 of 3` position counter, a footer legend, or a
hint. Both titles honor their `Line` alignment; unset, the top title is
flush-left and the bottom title flush-right. Titles begin one cell after the
corner and truncate before the opposite corner, matching ratatui `Block`.
The stylesheet's panel padding participates in measurement and rendering;
`.padding(...)` on this instance takes precedence.
[API](https://docs.rs/tuika/latest/tuika/components/struct.Boxed.html)
<img src="demos/boxed.png" width="880" alt="Boxed demo">
```rust
use tuika::prelude::*;
view! {
boxed(title = " title ", title_bottom = " 1/3 ", border = BorderStyle::Rounded) {
node(child)
}
}
```
### `FocusScope`
A layout-transparent wrapper that renders its subtree with an explicit focus
flag. Focus lives on the render context and `paint` uses one root context, so a
`Flex` can't hand a single child `focused = true`; wrap each pane in a
`FocusScope` so the active one's `Boxed` border lights up while the others stay
dim — independently of the frame's root focus.
[API](https://docs.rs/tuika/latest/tuika/components/struct.FocusScope.html)
```rust
use tuika::prelude::*;
view! {
row(gap = 1) {
grow(1) { node(FocusScope::focused(element(Boxed::new(element(Text::raw("active")))))) }
grow(1) { node(FocusScope::unfocused(element(Boxed::new(element(Text::raw("idle")))))) }
}
}
```
### `StatusBar`
One row with left- and right-anchored segment groups.
[API](https://docs.rs/tuika/latest/tuika/components/struct.StatusBar.html)
<img src="demos/status_bar.png" width="880" alt="StatusBar demo">
```rust
use tuika::prelude::*;
view! {
node(StatusBar::new().left(left_spans).right(right_spans))
}
```
### `Scrollbar` + `VirtualWindow`
One clamped window model and one scrollbar renderer for vertical or horizontal
collections. `VirtualWindow::around` keeps an absolute selection visible;
`range()` lets a host fetch only the current records. `SelectList::windowed`
and `Table::windowed` accept that slice directly, preserving absolute selection
and scrollbar geometry without cloning the full collection.
[Scrollbar API](https://docs.rs/tuika/latest/tuika/components/struct.Scrollbar.html) ·
[VirtualWindow API](https://docs.rs/tuika/latest/tuika/components/struct.VirtualWindow.html)
<img src="demos/scrollbar.png" width="880" alt="Vertical and horizontal scrollbars representing the same virtual collection window">
```rust
use tuika::prelude::*;
let window = VirtualWindow::around(total, viewport_rows, state.selected());
```
## Interactive
Each pairs a rendered view with a host-persisted `*State` (the
`StatefulWidget` idiom): the state owns cursor/offset/selection and handles
events, the view borrows it for a frame.
### `Scroll` + `ScrollState`
A windowed view over long content with a scrollbar; `ScrollState` handles
paging, wheel scroll, and stick-to-bottom. The offset is also **host-drivable**:
`set_offset(n)` mirrors an app-owned scroll position into the view — the
vertical peer of `SelectState::select` — for event-loop apps that track their
own position. Content wider than the pane (logs, diffs, wide tables, deep paths)
**pans horizontally** with `set_x_offset(cols)` (bind to `h`/`l` or `←`/`→`),
bounded by `clamp_x` — the pan is width-aware, so wide/CJK glyphs never split.
`ScrollState::max_offset` / `max_x_offset` expose the in-range bounds for a host
driving the offsets itself. For prose, `.wrap(true)` reflows styled lines at the
assigned width before windowing; horizontal panning is disabled in that mode.
[API](https://docs.rs/tuika/latest/tuika/components/struct.Scroll.html)
<img src="demos/scroll.gif" width="880" alt="Scroll demo">
```rust
use tuika::prelude::*;
let mut state = ScrollState::new(); // held by the host across frames
state.handle(&event, content_h, viewport_h); // built-in wheel/paging, or…
state.set_offset(app.scroll_row); // …mirror an app-owned row, and
state.set_x_offset(app.scroll_col); // …pan wide lines left/right
state.clamp_x(widest_line_w, viewport_w); // keep the pan within the content
view! { node(Scroll::new(lines, &state).wrap(true)) }
```
#### Following a stream
Content that grows while it is being read — a transcript, a log tail, a
streaming answer — wants to show the newest rows *until the reader scrolls away
from them*. That is not a mode to implement; it falls out of two calls:
```rust
use tuika::prelude::*;
let mut state = ScrollState::new();
// Once per frame, after appending: pins the offset to the newest content while
// the state is stuck to the bottom, and leaves a scrolled-back reader alone.
state.clamp(content_h, viewport_h);
// Scrolling up releases the stick; reaching the bottom again re-arms it.
state.handle(&event, content_h, viewport_h);
// Read it back to tell the reader which they are.
let live = state.is_stuck_to_bottom();
```
`examples/markdown.rs` runs exactly this over a streaming `MarkdownState`.
### `ItemScroll`
The same viewport over **items** instead of lines: `Vec<Element>`, each measured
at the render width and stacked with an optional `gap`. Scrolling is by row, not
by item, so an entry taller than the space left clips at the viewport edge and
scrolls through it — which is what a chat transcript, a feed, or any history of
laid-out things needs. Reach for `Scroll` when the content really is lines
(logs, prose); reach for this when an entry is a panel, a table, a diff, or a
nested layout. `measure_height` reports the row count so the host can reconcile
its `ScrollState` before painting, and `windowed` takes just the visible slice
plus the true height for lists too long to measure every frame.
[API](https://docs.rs/tuika/latest/tuika/components/struct.ItemScroll.html)
<img src="demos/item_scroll.gif" width="880" alt="ItemScroll demo">
```rust
use tuika::prelude::*;
let content_h = ItemScroll::measure_height(&items, width, 1, true, &ctx);
state.clamp(content_h, viewport_h); // reconcile before the paint
view! { node(ItemScroll::new(items, &state).gap(1)) }
```
### `Viewport` + `ScrollState`
A two-dimensional clipped window over any child `Element`, rather than only
line content. The host supplies the child's full cell extent and persists
vertical/horizontal offsets in `ScrollState`; optional right and bottom
scrollbars track the clamped window. Wide grapheme clusters are never painted
halfway across either clipped edge.
[API](https://docs.rs/tuika/latest/tuika/components/struct.Viewport.html)
```rust
use tuika::prelude::*;
let view = Viewport::new(element(markdown_or_grid), Size::new(120, 80), &scroll)
.horizontal_scrollbar(true);
```
### `Form` + `FormField` + `FormState`
Responsive labeled controls with help and validation rows. Labels share a
column on wide terminals and stack above controls on narrow terminals.
`FormState` handles focus traversal and submit/cancel outcomes; control values
remain in their normal host-owned state.
[API](https://docs.rs/tuika/latest/tuika/components/struct.Form.html)
```rust
use tuika::prelude::*;
let form = Form::new(vec![
FormField::new("Name", element(name_input)).help("Shown publicly"),
FormField::new("Mode", element(mode_select)).error(validation_error),
], &form_state);
```
### `Scene` + `Dialog`
`Scene` owns a base tree and ordered overlays. Screen anchors place dialogs and
other independent layers; `SceneOverlay::target` uses a `RectProbe` to follow a
laid-out trigger for popovers, menus, and tooltips, with side selection,
alignment, gap, edge-aware flipping, and screen clamping. `Dialog` builds a
centered modal from ordinary Tuika elements, with optional action hints,
min/max sizing, clear or dimmed backdrops, and top-layer focus ownership.
[Scene API](https://docs.rs/tuika/latest/tuika/scene/struct.Scene.html) ·
[Dialog API](https://docs.rs/tuika/latest/tuika/components/struct.Dialog.html)
<img src="demos/primitives.gif" width="880" alt="Owned dialog containing a responsive form and horizontally panning custom-drawn viewport">
```rust
use tuika::prelude::*;
let scene = Scene::new(element(base)).dialog(
Dialog::new("Confirm", element(Text::raw("Continue?")))
.key_hints([("enter", "yes"), ("esc", "no")])
.dim_backdrop(true)
.focus_owner("confirm"),
);
```
### Dialog presets
`ConfirmDialog`, `ChoiceDialog`, `MultiChoiceDialog`, and `InputDialog` assemble
the common modal flows from `Dialog`, selection controls, and text input. Their
paired state types remain host-owned and return the same `InputOutcome` used by
the lower-level components. Every preset converts to `Dialog`, so it works
with `Scene::dialog` and remains customizable through its builders.
[Confirm API](https://docs.rs/tuika/latest/tuika/components/struct.ConfirmDialog.html)
<img src="demos/dialog_presets.gif" width="880" alt="Dialog preset demo cycling through confirm, choice, multi-choice, and input dialogs">
```rust
use tuika::prelude::*;
let mut state = ConfirmDialogState::new(); // Cancel is the safe default
let outcome = state.handle(&event);
let scene = Scene::new(element(base)).dialog(
ConfirmDialog::new("Apply changes?", "Update three files?", &state)
.confirm_label("Apply")
.focus_owner("confirm")
.into_dialog(),
);
```
### `DrawView` / `CanvasView`
A closure-backed escape hatch for custom cell drawing. Its callback receives
the assigned area, clipped `Surface`, and `RenderCtx`, and composes as a normal
view. For application regions that also need custom intrinsic measurement,
`view_fn(measure, render)` is available from the crate root and prelude; both
closures may borrow frame-scoped application state.
[Inline view API](https://docs.rs/tuika/latest/tuika/fn.view_fn.html) ·
[Draw view API](https://docs.rs/tuika/latest/tuika/view/struct.DrawView.html)
```rust
use ratatui::layout::Rect;
use tuika::{RenderCtx, Surface};
use tuika::view::DrawView;
let chart = DrawView::new(
|area: Rect, surface: &mut Surface<'_>, ctx: &RenderCtx<'_>| {
surface.set_string(area.x, area.y, "▁▃▆█", ctx.theme.success_style());
},
);
```
### `SelectList` + `SelectState`
A selectable list; `SelectState` navigates with the arrow keys (wrapping),
confirms on Enter, cancels on Esc. `new()` and `default()` select the first row;
`SelectState::unselected()` starts cursorless, and `state.select(None)` clears an
existing selection so neither caret nor highlight is drawn. `.selection_style(style)`
overrides the theme selection style for one list. `handle_with` accepts a
`SelectNavigation` policy; `SelectNavigation::common()` enables j/k, Ctrl+N/P,
Tab/Shift+Tab, and numeric shortcuts. `handle_mouse` hit-tests explicit list
bounds and a viewport offset. `MultiSelectState` adds Enter/Space/click toggling
for pickers that retain several checked items. The runnable
[`select` example](https://github.com/everruns/tuika/blob/main/examples/select.rs)
combines every navigation mode in one picker.
[API](https://docs.rs/tuika/latest/tuika/components/struct.SelectList.html)
<img src="demos/select.gif" width="880" alt="SelectList demo">
```rust
use ratatui::style::{Color, Style};
use tuika::prelude::*;
let mut state = SelectState::unselected();
let style = Style::default().fg(Color::Blue);
state.select(Some(0)); // select a row when the host is ready
view! { node(SelectList::new(items, &state).selection_style(style)) }
```
### `CompletionPalette` + `CompletionState`
A reusable completion surface for slash commands, mentions, files, models, or
any host-provided candidates. `CompletionState::sync` fuzzy-ranks labels,
details, and hidden keywords; changed queries select the best result, while an
unchanged query preserves selection across candidate refreshes. The selected
`CompletionItem` exposes replacement text for the host to insert. Use
`show_query(true)` for a standalone command palette, or omit it for an editor-
anchored popup.
[API](https://docs.rs/tuika/latest/tuika/components/struct.CompletionPalette.html)
<img src="demos/completion_palette.gif" width="880" alt="CompletionPalette filtering slash commands">
```rust
use tuika::prelude::*;
let items = vec![
CompletionItem::new("model").detail("Choose a model").replacement("/model"),
CompletionItem::new("status").detail("Show session status").replacement("/status"),
];
let mut state = CompletionState::new();
state.sync(active_token.query(), &items);
let palette = CompletionPalette::new(&items, &state).title("Commands");
if state.handle(&event) == InputOutcome::Submitted {
editor.replace_token(&active_token, state.selected(&items).unwrap().replacement_text());
}
```
### `Table` + `SelectState`
The multi-column peer of `SelectList` — the widget behind repo/branch/worktree
browsers, process and container lists, and file explorers: a header row,
per-column width policy, a full-row selection highlight, a caret gutter, and
windowed scrolling. Column widths come from the same flexbox `solve` as every
other container — a `Column` is `fixed`, `auto` (widest cell), or `flex`
(shares leftover width). Selection reuses `SelectState`, so a list and a table
share one state type. The table windows to its assigned height by default;
`.viewport(rows)` is only an optional upper bound. Chrome follows the theme by
default but is overridable (the `Boxed::border_color` pattern): `.caret(char)`
sets the gutter marker,
`.header_style(Style)` restyles the header, `.selection_style(Style)` controls
one table's selection band (including modifiers), and
`.preserve_selection_fg(true)` keeps color-coded columns' own colors under the
selection highlight.
[API](https://docs.rs/tuika/latest/tuika/components/struct.Table.html)
```rust
use ratatui::style::{Color, Style};
use ratatui::text::Line;
use tuika::prelude::*;
let mut state = SelectState::new();
state.handle(&event, rows.len());
let style = Style::default().fg(Color::Blue);
let columns = vec![Column::auto("branch"), Column::fixed("ahead", 5), Column::flex("subject", 1)];
view! { node(Table::new(columns, rows, &state).selection_style(style).caret('▶')) }
```
### `KeyedTable` + `KeyedSelectState`
A virtualized table for large, changing host collections. It borrows domain
rows for one frame—either directly from a slice or through a
`KeyedRowSource<K>` that maps visible indices into authoritative storage—and
calls each `KeyedColumn` only for the visible window. Row data is not cloned
into widget-owned cells. `KeyedSelectState<K>` and
`KeyedMultiSelectState<K>` store application keys rather than positions, so a
selection follows the same record through reorder, insertion, filtering, and
streaming refreshes. Keys must be unique within the authoritative collection.
Filtering preserves absent keys; call `retain_present`
or `retain_present_source` with the authoritative collection when records are
truly deleted.
Columns support fixed, auto, and flex sizing, trailing alignment,
`hide_below(width)` breakpoints, and optional-column shedding. Styled borrowed
`Line`s remain styled, with `preserve_selection_fg(true)` retaining semantic
cell colors under the cursor band, while the built-in caret/check gutter covers
common leading indicators. Keyboard aliases reuse `SelectNavigation`; Page
Up/Down, Home/End, wheel scrolling, explicit mouse hit-testing, and configurable
scroll margin share `VirtualWindow` geometry. Hosts with a key-to-position index
can pass `selected_index` to avoid a collection scan without making the index
persistent identity.
For searchable application rows, `KeyedRowSource::key_eq` compares a projected
row directly with the owned selection key. Composite identity such as
`(Agent, session_id)` therefore needs no copied key per visible row, while
`NavigableKeyedRowSource::key` materializes one only when keyboard or mouse
input selects a row. The `*_indexed` column constructors receive the visible
row index, which joins parallel fuzzy positions or other decoration metadata
without a cached wrapper model. The runnable
[`keyed_table` example](https://github.com/everruns/tuika/blob/main/examples/keyed_table.rs)
uses an AGF-shaped `Vec<Session>` plus `Vec<usize>` visible order and parallel
fuzzy positions; it reorders, filters, inserts, and deletes rows while
composite-key selection stays stable.
[API](https://docs.rs/tuika/latest/tuika/components/struct.KeyedTable.html)
<img src="demos/keyed_table.gif" width="880" alt="Keyed table selection following a row through reorder and filtering">
```rust
use tuika::prelude::*;
struct Job { id: u64, name: String }
fn key(job: &Job) -> &u64 { &job.id }
fn name(job: &Job) -> Line<'_> { Line::from(job.name.as_str()) }
let state = KeyedSelectState::with_selected(42);
let columns = vec![KeyedColumn::flex("job", 1, name)];
let table = KeyedTable::new(columns, &jobs, key, &state);
```
An indirect source replaces per-frame wrapper construction. Counting nonblank
Rust source lines exactly as shown, the per-frame caller falls from **20 LOC to
8 LOC** (−60%); the source's trait implementation is one-time and shared by
rendering, keyboard navigation, mouse hit-testing, and deletion reconciliation.
Before—copied composite keys and metadata in wrapper rows:
```rust
struct VisibleSession<'a> {
session: &'a Session,
key: SessionKey,
fuzzy: &'a [usize],
}
session: &sessions[source],
key: sessions[source].key(),
fuzzy: &fuzzy[row],
}
}).collect::<Vec<_>>();
let table = KeyedTable::new(
vec![KeyedColumn::flex("summary", 1, |row: &VisibleSession<'_>| {
highlighted(&row.session.summary, row.fuzzy)
})],
&rows,
|row| &row.key,
&state,
);
```
After—authoritative rows and parallel metadata stay in place:
```rust
let source = SessionRows { sessions: &sessions, visible: &visible };
let table = KeyedTable::source(
vec![KeyedColumn::flex_indexed("summary", 1, |row, session: &Session| {
highlighted(&session.summary, &fuzzy[row])
})],
&source,
&state,
);
```
### `Tabs` + `TabsState`
A one-line tab strip; `TabsState` handles left/right and tab navigation.
[API](https://docs.rs/tuika/latest/tuika/components/struct.Tabs.html)
<img src="demos/tabs.gif" width="880" alt="Tabs demo">
```rust
use tuika::prelude::*;
let mut state = TabsState::default();
state.handle(&event, labels.len());
view! { node(Tabs::new(labels, &state)) }
```
### `TabSelect` + `TabSelectState`
A value-selecting segmented control (as opposed to `Tabs`, which is navigation
chrome): moving the cursor changes the selected value immediately, and
Enter/Space activates it. `handle` returns the shared `InputOutcome`,
distinguishing a change from submission while the state owns the selected value.
[API](https://docs.rs/tuika/latest/tuika/components/struct.TabSelect.html)
<img src="demos/tab_select.gif" width="880" alt="TabSelect demo">
```rust
use tuika::prelude::*;
let mut state = TabSelectState::default();
state.handle(&event, labels.len());
view! { node(TabSelect::new(labels, &state)) }
```
### `Slider` + `SliderState`
A one-row value picker over a numeric range with a filled track and thumb.
`SliderState` clamps to `min..=max`, steps via the arrow keys (Home/End snap to
the bounds), and `set_ratio` maps a click position to a value.
[API](https://docs.rs/tuika/latest/tuika/components/struct.Slider.html)
<img src="demos/slider.gif" width="880" alt="Slider demo">
```rust
use tuika::{Slider, SliderState, view};
let mut state = SliderState::new(0.0, 100.0, 40.0).step(5.0);
state.handle(&event);
view! { node(Slider::new(&state).label(&state)) }
```
### `TextInput` + `TextInputState`
A multi-line edit model: buffer, cursor, editing, and soft-wrap. `TextInput`
renders a snapshot; the host places the terminal cursor from
`TextInputState::cursor_screen`. Configure Enter vs Shift+Enter with
`TextInputMode` (`SubmitOnEnter` by default): the other chord inserts a newline.
Ctrl+J always inserts a newline (raw-mode LF from terminals without enhanced
keyboard reporting). `placeholder` fills an empty buffer, and `highlights` paints
host-computed `TextSpan` ranges over the text. Cursor and span coordinates remain
char indices for host interoperability; movement and deletion keep grapheme
clusters intact, and wrapping/cursor placement use terminal-cell width so CJK
and emoji align with the rendered grid.
For search fields and command bars, `SingleLineInputState` wraps the same editor
but guarantees one line: setters and paste normalize CR/LF to spaces, Enter and
Ctrl+J submit, and `text()` returns a borrowed `&str` without allocation. Render
it with `TextInput::new(state.as_text_input())`.
Like the other interactive state types, `handle` returns `InputOutcome`:
`Changed` means persistent state moved, `Consumed` means a recognized action hit
a bound, `Submitted`/`Cancelled` report lifecycle intent, and only `Ignored`
should continue through input routing. The edited text remains in the state.
[API](https://docs.rs/tuika/latest/tuika/components/struct.TextInput.html)
<img src="demos/textinput.gif" width="880" alt="TextInput demo">
```rust
use tuika::{TextInput, TextInputMode, TextInputState, view};
let mut state = TextInputState::from_text("");
state.set_mode(TextInputMode::SubmitOnEnter);
view! {
boxed(title = " commit message ") {
node(TextInput::new(&state))
}
}
```
#### Inline tokens: `@mentions`, `/commands`, anything
A composer usually wants more than plain text: a `@` that completes a file, a
`/` that opens a command palette, a `#` that links an issue. `Trigger` declares
*where* an opening character counts (`TriggerAnchor::{Anywhere, WordStart,
LineStart, BufferStart}`) and whether the token stops at whitespace; the state
finds them. What they **mean** — which popup opens, what completes, how they are
colored — stays in the host, so any app can define its own set.
```rust
use tuika::{TextInput, TextInputState, Trigger, TriggerAnchor};
let triggers = [
Trigger::new('/').anchor(TriggerAnchor::BufferStart), // a command palette
Trigger::new('@'), // a file mention
];
if let Some(token) = state.active_token(&triggers) { // cursor inside one?
let rows = complete(token.trigger, token.query()); // host's own source
// …and on confirm, splice the choice back in:
state.replace_token(&token, "@src/lib.rs ");
}
// Color every token, whether or not the cursor is in it.
```
## Notifications & console
### `Toasts` + `ToastList`
A transient notification stack with frame-driven expiry: each toast carries a
remaining lifetime in frames, `tick()` decrements them, and one is dropped at
zero. Four severity levels select a semantic accent role and glyph, so one
stylesheet or resolver restyles every notification. Place a `ToastList` in a
corner overlay.
[API](https://docs.rs/tuika/latest/tuika/components/toast/struct.Toasts.html)
<img src="demos/toast.png" width="880" alt="Toasts demo">
```rust
use tuika::{ToastLevel, ToastList, Toasts, view};
let mut toasts = Toasts::new(4);
toasts.push(ToastLevel::Success, "Saved");
toasts.tick(); // once per frame; drops expired toasts
view! { node(ToastList::new(&toasts)) }
```
### `Console` + `ConsoleLog`
Capture `println!`/`tracing` output into a capped ring buffer and show it in a
toggleable overlay. `ConsoleLog` is a cheap, cloneable, `Send`/`Sync` handle that
implements `std::io::Write`, so it drops straight into a logging pipeline; the
`Console` view tails the most recent lines.
[API](https://docs.rs/tuika/latest/tuika/components/console/struct.ConsoleLog.html)
<img src="demos/console.png" width="880" alt="Console demo">
```rust
use tuika::{Console, ConsoleLog, view};
let log = ConsoleLog::new(500);
```
## Banners, codes & pixels
### `AsciiFont`
Large "figlet-style" block-letter banners from an embedded 5-row font (A–Z, 0–9,
punctuation; case-insensitive). Themed accent by default, overridable.
[API](https://docs.rs/tuika/latest/tuika/components/ascii_font/struct.AsciiFont.html)
<img src="demos/ascii_font.png" width="880" alt="AsciiFont demo">
```rust
use tuika::{AsciiFont, view};
view! { node(AsciiFont::new("TUIKA")) }
```
### `QrCode`
A QR code drawn with half-block cells. The bundled encoder is byte-mode, versions
1–4 (up to 78 bytes at ECC Low — URLs, Wi-Fi credentials, tokens), with
Reed-Solomon, interleaving, and masking; larger payloads can be encoded elsewhere
and handed to `QrCode::from_matrix`.
[API](https://docs.rs/tuika/latest/tuika/components/qr/struct.QrCode.html)
<img src="demos/qr.png" width="880" alt="QrCode demo">
```rust
use tuika::{QrCode, QrEcc, view};
let qr = QrCode::encode("https://everruns.com", QrEcc::Medium).expect("fits v1–4");
view! { node(qr) }
```
### `FrameBuffer` + `FrameBufferView`
A mutable RGBA pixel canvas — `set`/`blend`/`fill_rect`/`blit`, a per-pixel
`shade` shader post-pass, and `Sprite` spritesheet frames. `FrameBufferView`
packs two vertical pixels per cell with a half-block, so it renders in any
terminal; `to_image_data()` hands the same pixels to the Kitty/iTerm2/Sixel
graphics protocols for a crisp render.
[API](https://docs.rs/tuika/latest/tuika/framebuffer/struct.FrameBuffer.html)
<img src="demos/framebuffer.gif" width="880" alt="FrameBuffer demo">
```rust
use tuika::{FrameBuffer, FrameBufferView, view};
let mut fb = FrameBuffer::new(64, 32);
fb.clear([20, 20, 40, 255]);
fb.fill_rect(8, 8, 16, 16, [240, 90, 90, 255]);
view! { node(FrameBufferView::new(&fb, 64, 16)) }
```
### `KeyHints`
Priority-aware footer hints fit only complete key/action pairs. Contextual
bindings with higher keymap layer priority survive first as width contracts.

[API](https://docs.rs/tuika/latest/tuika/components/struct.KeyHints.html) · [Source](https://github.com/everruns/tuika/blob/main/src/components/key_hints.rs)
### `KeymapHelp`
A complete, vertically scrollable help view generated from the same active,
labeled keymap declarations used for dispatch and footer hints.

[API](https://docs.rs/tuika/latest/tuika/components/struct.KeymapHelp.html) · [Source](https://github.com/everruns/tuika/blob/main/src/components/key_hints.rs)
## See also
- [API documentation](https://docs.rs/tuika) — the complete component reference,
including helpers without a standalone demo (`Spacer`, `Responsive`,
`Constrained`, `Wrap`).
- [Markdown guide](markdown.md) — streaming, GFM tables, highlighting, links,
images, and inline HTML, in one place.
- [Runnable examples](../examples/) — enter the alternate screen; quit with `q`/`esc`.
- [README](../README.md) — the model behind the toolkit.