Skip to main content

dart_tui 3.0.0 copy "dart_tui: ^3.0.0" to clipboard
dart_tui: ^3.0.0 copied to clipboard

Elm-style terminal UI framework for Dart — Model–Update–View, async commands, 27+ components, Lipgloss-inspired styling, spring animation, and gum-style helpers.

Changelog #

3.0.0 - 2026-10-17 #

This major release makes raw mode behave like Bubble Tea on macOS and Linux, decodes input one Unicode scalar at a time, validates timers and forms in every build mode, and removes two leftover APIs. It also fixes the hang that kept every program reading default stdin alive after it quit (#15). The full macOS validation is in docs/quality/2026-10-17-macos-3.0.0-validation.md.

Breaking changes #

  • View.setContent removed. Assign the field instead: view.content = 'Hello';.
  • Form.fieldIndexForTest removed. Test observable state instead, such as form.values or form.view().content.
  • Ctrl+C, Ctrl+Z and other control keys are key messages on macOS and Linux. Raw mode now also clears ISIG, IEXTEN and IXON. Ctrl+C no longer raises SIGINT, so Program no longer turns it into InterruptMsg and quits for you. Ctrl+Z no longer stops the process, which used to leave the terminal broken. Handle the keys in update. kill -INT still delivers InterruptMsg. Ctrl+, Ctrl+S, Ctrl+Q, Ctrl+V and Ctrl+Y also arrive as keys.
    if (msg is KeyMsg) {
      switch (msg.key) {
        case 'ctrl+c':
          return (this, () => quit());
        case 'ctrl+z':
          return (this, suspend); // optional job control
      }
    }
    
  • One key event per Unicode scalar. Adjacent printable bytes are no longer merged into one KeyPressMsg. An unbracketed paste of hello is five events. A grapheme cluster such as 👨‍👩‍👧 or e + U+0301 arrives as several events. Malformed UTF-8 produces unknown keys instead of stalling input. Append msg.keyEvent.text for each event, and handle PasteMsg for pasted text.
  • Timer helpers validate durations when called. every throws ArgumentError for a zero or negative interval. tick and tickWithId throw for a negative delay. Before, every(Duration.zero, ...) failed later with a division by zero.
  • Forms validate their input at construction in every build mode. Form throws ArgumentError for no groups, an empty group, or duplicate field keys across groups. Field.selectOf and Field.multiSelectOf throw when given both or neither of options / optionsFor. Multi-select fields throw on duplicate option values. Before, only debug asserts checked some of these.
  • Default stdin belongs to the main isolate. A Program without withInput must run in the main isolate; elsewhere run() throws StateError. Readers in several isolates used to race for input and could close stdin for the whole process.
    await Isolate.run(() => Program(options: [withInput(null)]).run(model));
    
  • SIGTERM quits gracefully. It enqueues QuitMsg, so the terminal is restored and run() returns. A withFilter that vetoes QuitMsg also vetoes SIGTERM. Before, the process died with the terminal left in the alternate screen.
  • meta is no longer a dependency. If you imported package:meta through dart_tui, add it to your own pubspec.yaml.

Behavior changes #

  • Programs exit when run() returns. A helper isolate reads default stdin, so stdin no longer keeps the process alive after the last program. Sequential programs (prompts, gum helpers) still share input, stdin stays open afterwards, and withInput(stdin) uses the same shared reader.
  • Commands end with the program. Timers that a command starts (tick, every, Future.delayed, ...) are cancelled when the program ends.
  • execProcess owns the terminal input. While an inherited child runs, no program reads stdin. A child started right after startup no longer receives the terminal's replies to the startup queries; the exec waits up to 500 ms for them, once.
  • sequence no longer blocks input. Its commands still run in order, but other messages are processed while a step is pending. A sequence stops after a step throws or returns QuitMsg/InterruptMsg.
  • Text widgets accept bracketed paste. Focused TextInputModel, TextAreaModel and promptInput insert PasteMsg content with control characters removed. A Form routes the paste to the active field. Forward PasteMsg to your widgets as well as KeyMsg.
  • Multi-select and dynamic selects keep selections by value. Values that disappear from optionsFor are dropped, and reordered options keep the selection. An untouched optionsFor select submits its first option, like a static select, instead of null.
  • Form focus skips hidden fields, hidden groups and notes, including at the start of the form, and hidden groups never receive input.
  • More keys are named. ESC + key arrives as alt+<key> (alt+a, alt+enter, ctrl+alt+a) instead of unknown. NUL is ctrl+space; 0x1C–0x1F are ctrl+\, ctrl+], ctrl+^ and ctrl+_; SS3 arrows are up/down/left/right; SGR wheel-left/right decode as wheelLeft/wheelRight.
  • The final frame is rendered on quit, and leaving the alternate screen no longer prints a blank line.
  • Mouse wheel events scroll ListModel, TableModel and TreeModel.
  • ListModel filter mode: Up/Down and Ctrl+P/Ctrl+N move through the matches while typing, so gum filter() returns the highlighted match.
  • FilePickerModel shows load errors (error) instead of an empty list, ignores keys while a directory loads, clamps cursor, scrollOffset and height, and adds copyWith(clearSelected: true).

Fixes #

  • Inline (non-alt-screen) programs leave the shell prompt directly below the view on exit. Before, it landed on the last changed row, inside the view.
  • Typing a combining mark or a multi-scalar emoji no longer crashes TextInputModel or misplaces text in TextAreaModel.
  • Backspace in list filters and promptInput deletes a whole grapheme, and promptInput accepts Space.
  • List mouse clicks select the right item when only some items have descriptions; navigation is safe with an empty list or no filter matches.
  • Field.input(placeholder:) renders; optionsFor selects show their options before the first edit.
  • Table headers align with their rows, and the selected tree branch keeps its highlight.
  • Canvas layers with equal z-index keep their paint order.
  • Input stream errors quit cleanly and restore the terminal, and a throttled frame is never written after the terminal is released or the program is killed.
  • Timer errors show the rejected duration.
  • Fixed several examples: shopping_list, showcase, http, package_manager, cursor_model, gmail, canvas, isbn_form, list_mutation and file_picker.

Performance #

  • TextInputModel re-segments only the end of the value when text is appended: 25,000 single-character inserts take 0.95 s, down from 1.7 s in 2.1.0.

Tooling and docs #

  • A new sdk-floor CI job runs analysis and tests on Dart 3.5.4, and the dev dependency ranges now resolve there.
  • PTY smoke tests require each example to exit on its own after quit, and cover paste and Space.
  • tool/compile_examples.sh reports failed compiles.
  • Re-recorded all 60 VHS example GIFs.

Known limitations #

  • Inline (non-alt-screen) rendering still starts at row 1 and can overwrite earlier shell output (#18).
  • On Windows, the first input meant for an execProcess child can reach the program instead.

2.1.0 - 2026-09-10 #

Performance and terminal-lifecycle follow-up to 2.0.0. No breaking API changes.

Performance #

  • Faster input decoding, layout, and rendering on large pastes, viewports, and text widgets.
  • Cache TextAreaModel logical lines across cursor-only copies.
  • Fast-path textWidth for ASCII-only strings.
  • make bench-hotpath compares naive Dart with dart_tui; make bench-startup-pty measures first-visible startup.

Fixes #

  • Bracketed paste no longer mangles multi-byte UTF-8.
  • Restore cooked TTY mode before stdin cancel on exit.
  • Skip \x1b[?1049l on shutdown when alt screen was never entered.

Docs #

  • Performance guide and README hot-path table.
  • Re-recorded all 60 VHS example GIFs.

2.0.0 - 2026-08-08 #

This major release hardens the terminal runtime, ports the selected Bubble Tea capabilities through P9, and removes the temporary compatibility APIs so the package has one clear model, styling, and program-configuration path.

Breaking changes #

  • Models: removed the TeaModel alias. Extend or implement Model directly.
  • Keys: removed the LegacyKeyMsg alias. Use KeyPressMsg for press events or the shared KeyMsg base type.
  • Styles: removed the raw ANSI TuiStyle helpers. Use immutable Style values such as const Style(isBold: true).render(text).
  • Program configuration: removed ProgramOptions and the parallel programOptions / programSettings parameters. Program, forms, prompts, and gum helpers now accept one List<ProgramOption> named options. Use withAltScreen(), withTickInterval(...), withHideCursor(...), and withLogFile(File(...)) for the former struct fields.

Bubble Tea parity #

  • Added declarative terminal colors, native progress reporting, keyboard options, and view-level mouse callbacks.
  • Added Kitty progressive-keyboard negotiation and modern key press, repeat, and release events.
  • Added Unicode Core negotiation plus shared grapheme-aware terminal width handling across input, styling, and rendering.
  • Expanded the viewport with gutters, per-line styles, search highlights, filling, and horizontal navigation.
  • Added dynamically sized, visually wrapped textarea behavior.
  • Added multi-stop, scaled, and callback-driven progress colors.
  • Added rich underline styles, OSC 8 hyperlinks, and ANSI-state-safe layout operations.
  • Added explicit dark/light component style factories and immutable suggestion, cursor, and viewport introspection APIs.

Runtime correctness and security #

  • Made cell diffs width-aware for CJK, emoji, and other double-width graphemes, while reducing cell parsing from quadratic to linear work.
  • Made declarative cursor position, shape, blink, and color reach the terminal.
  • Correctly consume OSC, DCS, APC, PM, and SOS control strings without leaking payload bytes into rendered cells.
  • Made Program.kill() wake idle programs and restore terminal state.
  • Corrected Unicode capability-report semantics and sanitized window-title OSC payloads.
  • Preserved cumulative SGR and OSC 8 hyperlink state in CellRenderer, made multi-grapheme input edits atomic, and restored viewport line styles around highlighted spans.

Release quality and tooling #

  • Added real-PTY tests for kill, external cancellation, resize, suspend/resume, and terminal restoration.
  • Centralized alternate-screen, cursor, focus, paste, and mouse transitions in one terminal-mode state machine shared by both renderers.
  • Aligned local and CI analysis on one clean repository-wide boundary.
  • Made plain Dart the default across Make targets, examples, VHS tapes, and website source mirrors; FVM remains an optional override.
  • Refreshed meta, lints, and test constraints to their current resolvable releases.
  • Moved README previews to the hosted documentation assets and excluded local GIF binaries from the pub archive.

Documentation and examples #

  • Upgraded the documentation site to patched Next.js 16.3.0 with dedicated audit, typecheck, and production-build CI.
  • Refreshed the component gallery, guides, API snippets, and all 60 tape-backed example previews for the 2.0 API.

1.4.0 #

Forms (huh-style) #

A composable, immutable, key-based forms subsystem — the equivalent of Charm's huh.

  • Form (forms/form.dart) — an immutable TeaModel you can embed (exposes submitted/cancelled/values), or run one-shot with await form.run() (returns FormValues?, null if cancelled). Groups of fields render one page at a time as a wizard, with a Group N/M indicator.
  • Field types via the Field factory: input, password, text (multiline), file, select / selectOf<T>, multiSelect / multiSelectOf<T>, confirm, note. Text-ish fields reuse TextInputModel/TextAreaModel/FilePickerModel.
  • Key-based values — FormValues.get<T>(key); hidden fields are excluded from the result.
  • Validation — per-field validate with inline errors; blocks advancing and submit.
  • Dynamic / conditional fields — any field or group takes hidden: (FormValues) => bool; titles and select options can be computed from other fields' values (titleFor, optionsFor), recomputed on every change with selection clamping.
  • Navigation — Tab/Enter advance (Enter inserts a newline in multiline text; Tab/Ctrl+D advance there), Shift+Tab back, Esc/Ctrl+C cancel.
  • FormStyles — Catppuccin Mocha defaults.
  • New form.dart example + VHS tape.

Fixes #

  • TextInputModel / TextAreaModel: typing a space inserted the literal string "space" (and F-keys inserted "f1", etc.) because the default branch inserted the keystroke name rather than the character. Now only unmodified printable runes are inserted, using the actual rune text — so text fields (and form inputs) accept spaces correctly.

Docs #

  • New documentation website — a searchable gallery of every component, block and guide with live GIF previews and copyable source. Lives in website/ (excluded from the published package via .pubignore).

1.3.0 #

New components & helpers #

  • Spring (bubbles/spring.dart) — harmonica-style damped-harmonic-oscillator for smooth eased motion of any scalar (progress, scroll, cursor). Pure math, no terminal I/O: Spring(fps:, frequency:, damping:).update(pos, vel, target) plus fpsToDelta().
  • FileLog (log.dart) — append timestamped diagnostics to a file. Because a running Program owns stdout, print() corrupts the UI; write here and tail -f instead. FileLog.none() discards.
  • gum-style one-shot helpers (gum.dart) — filter() (interactive fuzzy picker), spin() (run a spinner while awaiting a Future, returns its result), and pager() (scrollable viewer). Each takes a programOptions list for headless testing.
  • ListModel runtime mutation API — withItems, appendItem, insertItem, removeItemAt, setItemAt, select, and selectedIndex; all clamp the cursor.
  • Readline / emacs editing keys for TextInputModel and TextAreaModel — ctrl+a/ctrl+e (home/end), ctrl+b/ctrl+f (char left/right), alt+←/alt+→ (word motion), and ctrl+w / alt+backspace (delete previous word).

Examples #

Five new runnable examples (each with a VHS tape): spring, gum (filter/spin/pager), list_mutation, readline, and file_log.

Performance #

Hot-path allocation and CPU reductions on the per-frame render and per-keystroke paths (all behaviour-preserving, covered by regression tests):

  • stripAnsi fast-path (skip the regex when there is no escape) plus a single-grapheme width helper — the width/wrap/truncate/pad path no longer runs a regex per grapheme.
  • ViewportModel reuses its wrapped lines across scrolling instead of re-wrapping every frame.
  • AnsiRenderer and CellRenderer skip the grid rebuild / diff walk on identical frames; the redundant per-frame list copy is gone.
  • ListModel.filteredItems is memoised; TreeModel reuses its flattened node list on cursor-only changes.
  • The render throttle uses a precomputed frame budget and a monotonic Stopwatch.

Fixes #

  • Prompts no longer hang on cancel. promptSelect / promptConfirm / promptInput set finished on Esc/Ctrl+C but never quit, and raw mode delivers Ctrl+C as a byte (not SIGINT) — so cancelling hung forever. They now quit cleanly and return null. The three prompt helpers also gained a programOptions parameter for headless/scripted use.

Quality #

  • Test suite grown from 423 to 560+ cases; measured lib/ line coverage raised from ~67% to 92%.
  • New make coverage [FLOOR=90] target and a CI coverage gate (Linux/macOS) that fails below the floor, honouring // coverage:ignore-* markers for genuinely-untestable code (Win32 FFI, raw-mode TTY, OS signals).

1.2.0 #

New components #

  • CursorModel (bubbles/cursor.dart): in-line blinking cursor widget with three display modes — CursorMode.block (█), CursorMode.underline (_), and CursorMode.bar (|). Toggles visibility on every TickMsg when blink: true. Useful for building custom text editors, prompts, or any UI that needs a visible insertion point independent of the real terminal cursor. Supports focus()/blur() to pause blinking, and withMode()/withBlink() builders.

  • MultiSelectModel (bubbles/multi_select.dart): scrollable checkbox list supporting multiple concurrent selections. Navigate with ↑↓ / jk, toggle with Space or x, select/deselect all with a, confirm with Enter. Features:

    • wrap: bool — cursor wraps at list boundaries
    • height — viewport limiting
    • showStatusBar — "N/Total selected" footer
    • selectedValues getter — returns custom value or falls back to label
    • MultiSelectStyles for full per-element theming (Catppuccin Mocha defaults)

New ProgramOption functions #

Seven new fluent option functions complement the existing ProgramOptions struct:

withAltScreen()                                    // enter alt-screen buffer at startup
withHideCursor([bool hide = true])                 // hide/show terminal cursor at startup
withTickInterval(Duration interval)                // emit TickMsg at a fixed interval
withMouseCellMotion()                              // enable button-event mouse tracking
withMouseAllMotion()                               // enable all-motion mouse tracking
withReportFocus()                                  // emit FocusMsg / BlurMsg on window focus
withWindowSize(int width, int height)              // inject fixed dimensions (useful in tests)

These compose with ProgramOptions and take precedence over it; defaultMouseMode and defaultReportFocus act as floor values so per-View overrides still work.

Style additions #

  • Border.normal — ASCII-art border (+, -, |) for environments without Unicode box-drawing support.
  • Per-side border flags — Border now has showTop, showRight, showBottom, showLeft (all default true). Use Style.withBorderSides({top, right, bottom, left}) or the pre-built helpers Border.topOnly, Border.bottomOnly, Border.sidesOnly to draw partial borders.
  • Border.copyWith() — produce modified Border instances without recreating all fields.
  • tabWidth: int field on Style (default 4) — \t characters are expanded to spaces before rendering. Use Style.withTabWidth(n) fluent builder.
  • marginBackground: RgbColor? field on Style — fills the margin area with a solid ANSI background colour. Use Style.withMarginBackground(color) fluent builder.
  • getWidth(String) (public) — visible terminal column width after stripping ANSI and counting double-wide characters.
  • getHeight(String) (public) — number of newline-delimited lines.
  • truncate(String, int) (public) — drop trailing visible columns to fit maxWidth; ANSI-safe.
  • truncateLeft(String, int) (public) — drop leading visible columns; ANSI codes in the kept portion are preserved.

Component navigation & mouse improvements #

  • ListModel — added pgup / ctrl+b, pgdown / ctrl+f, home / g, end / G key bindings; viewOffsetY field for click-to-select mouse handling; mouse wheel scrolling.
  • SelectListModel — added wrap: bool (cursor wraps at list boundaries).
  • TableModel — added viewOffsetY and mouse handling (wheel up/down, left-click to select with header offset).
  • TreeModel — added viewOffsetY and mouse handling (wheel scroll, left-click to move cursor); fixed ' ' space key mapping to 'space' keystroke.

New examples #

Example What it shows
cursor_model.dart CursorModel — all three blink modes side-by-side, toggle blink with b
multi_select.dart MultiSelectModel — toggle, select-all, confirm, display result

Tests #

10 new test files, 160+ new test cases:

File Coverage
spinner_test.dart SpinnerModel state transitions
select_list_test.dart SelectListModel navigation, wrap, view
progress_test.dart ProgressModel rendering and clamping
help_test.dart HelpModel expand/collapse, KeyMap
paginator_test.dart PaginatorModel navigation, bounds, view
cursor_model_test.dart CursorModel blink, focus, modes, view
multi_select_test.dart MultiSelectModel toggle, select-all, wrap, view
program_options_test.dart Integration: each ProgramOption emits correct ANSI sequences
input_decoder_test.dart TerminalInputDecoder — 48 edge-case / fuzz-style tests
style_properties_test.dart Property-based invariants for getWidth, getHeight, truncate, Style.render, joinH/V, stripAnsi

Other #

  • All 54 VHS GIFs regenerated from current kernel snapshots.

1.1.0 #

New features #

  • ListModel (bubbles/list.dart): full-featured scrollable list with fuzzy/subsequence filtering, keyboard navigation (↑↓ / jk), filter mode (/ to enter, Esc / Backspace to exit), viewport scrolling, optional descriptions, status bar (x/y items), and FullListStyles for per-element theming.
  • TabsModel (bubbles/tabs.dart): tabbed-interface component with (label, content) pairs, ←/→ / h/l / Tab / Shift+Tab navigation, and TabsStyles for active/inactive/divider/content theming.
  • SGR attributes — three new text decorations on Style:
    • isReverse (SGR 7) — swap foreground and background
    • isBlink (SGR 5) — blinking text
    • isOverline (SGR 53) — overline decoration
  • Style.inherit(parent) — fills every null field from a parent Style, enabling clean style composition without property repetition. All boolean SGR fields (isBold, isDim, isItalic, isUnderline, isStrikethrough, isReverse, isBlink, isOverline) changed from bool → bool? to support three-valued inheritance semantics.
  • underlineSpaces / strikethroughSpaces — control whether underline / strikethrough decorations extend over padding spaces (default true, matching Lipgloss).
  • borderForeground / borderBackground — independent RgbColor tinting for border characters, separate from text content color.
  • borderTitle / borderTitleAlignment — embed a title string in the top border edge with Align.left / .center / .right positioning.
  • wordWrap — Style(wordWrap: true) wraps at word boundaries before padding/border/constraint are applied; respects the width constraint.
  • transform — Style(transform: fn) applies an arbitrary String Function(String) to the rendered content (useful for upper-casing, truncation, etc.).
  • CompleteColor — per-profile color specification: CompleteColor(trueColor: …, ansi256: …, ansi: …); used via foregroundComplete / backgroundComplete on Style for correct downgrade at each profile level.
  • Border.hidden — a visible-but-blank border (spaces) that preserves box geometry without drawing any characters.
  • EdgeInsets.symmetric({vertical, horizontal}) and EdgeInsets.only({top, right, bottom, left}) — additional named constructors matching Flutter conventions.
  • Layout helpers now accept enums instead of raw double fractions:
    • joinHorizontal(AlignVertical, List<String>) — was double
    • joinVertical(Align, List<String>) — was double
    • place(width, height, Align, AlignVertical, content) — was two double args
    • placeHorizontal(width, Align, content) — new helper
    • placeVertical(height, AlignVertical, content) — new helper
  • Terminal control Cmd helpers (cmd.dart):
    • enterAltScreen() / exitAltScreen() — emit EnterAltScreenMsg / ExitAltScreenMsg
    • hideCursor() / showCursor() — emit HideCursorMsg / ShowCursorMsg
    • setWindowTitle(title) — Cmd that emits SetWindowTitleMsg
    • clearScrollArea() — emits ClearScrollAreaMsg
    • scrollUp([n = 1]) / scrollDown([n = 1]) — Cmd that emits ScrollMsg
  • Renderer interface extended: setAltScreen(bool), setCursorVisibility(bool), scroll(int, {bool up}) added to TeaRenderer and implemented by AnsiRenderer, CellRenderer, and NilRenderer.
  • stripAnsi(String) — public utility that strips all ANSI escape sequences from a string; previously internal-only.

New examples #

Example What it shows
word_wrap.dart Style.wordWrap at multiple widths with box, rounded, and thick borders + border title
border_style.dart All 6 Border variants, borderForeground / borderBackground, and all three borderTitle alignments
sgr_attrs.dart All 8 SGR text attributes; Style.inherit() composition
list_filter.dart ListModel with fuzzy filtering, descriptions, status bar, and selection

tabs.dart updated to use the exported TabsModel bubble instead of an inline reimplementation.

Tests #

9 new test files, 90+ new test cases: style_sgr_test.dart, style_border_test.dart, style_wordwrap_test.dart, canvas_test.dart, gradient_test.dart, adaptive_color_test.dart, cmd_terminal_test.dart, list_model_test.dart, tabs_model_test.dart.

Breaking changes #

  • joinHorizontal, joinVertical, and place now accept enum arguments (AlignVertical, Align) instead of raw double fractions. Migrate: 0.0 → AlignVertical.top, 0.5 → AlignVertical.middle, 1.0 → AlignVertical.bottom; 0.0 → Align.left, 0.5 → Align.center, 1.0 → Align.right.
  • All boolean SGR fields on Style (isBold, isDim, isItalic, isUnderline, isStrikethrough) changed from bool (default false) to bool? (default null). Existing code that reads these fields may need a null-aware comparison (style.isBold == true or style.isBold ?? false).

1.0.0+1 #

Bug fixes #

  • Terminal hang on exit: awaiting the stdin subscription cancel in the shutdown path so the Dart event loop is fully released before the process exits. Previously the unawaited cancel could leave stdin holding the event loop open, requiring a manual Ctrl-C to regain the shell prompt.
  • Terminal not restored on quit: flushing ANSI reset sequences (show cursor, exit alt-screen) before the process exits so the shell prompt appears on a clean line.

Other changes #

  • Enter / LF key fix: 0x0a (LF / \n) now correctly maps to KeyCode.enter, fixing silent key drops on Linux/WSL terminals that send LF instead of CR for Enter.
  • Batch render loop: all pending messages are drained before each render; a single render fires per batch, eliminating up to 16 ms of FPS-throttle lag per key press.
  • Deferred capability queries: CSI ?2026$y and OSC 11 are sent after the first rendered frame so startup is not delayed.
  • Makefile: make format (dart format check), make test, make analyze, make run/run-fast/kernels/bench/gifs/new-example.
  • Analyzer clean: resolved all strict_raw_type, unused_local_variable, prefer_const_constructors, library_private_types_in_public_api, and avoid_relative_lib_imports warnings.

1.0.0 #

New features #

  • CellRenderer: cell-level diff renderer using grapheme clusters (characters package) — only changed cells emit ANSI sequences, eliminating flicker.
  • Synchronized updates: CSI ?2026h/l wrapping for flicker-free frames on terminals that support it.
  • ExecMsg / execProcess(): run external processes (e.g. $EDITOR) with full terminal hand-off and optional exit-code callback.
  • TextAreaModel: multi-line editor with charLimit, ctrl+k/ctrl+u line-kill, cursor navigation.
  • ViewportModel: scrollable content pane with soft-wrap, atBottom/scrollPercent, keyboard navigation.
  • TableModel + TableColumn: tabular data viewer with header, separator, scrolling cursor, and optional row styles.
  • TimerModel: countdown timer, start()/stop()/reset() builders, TickMsg routing by id.
  • StopwatchModel: elapsed-time stopwatch with millisecond display.
  • KeyMap + KeyBinding + HelpModel: declarative keybinding registry; help UI with compact/full toggle.
  • FilePickerModel: async directory browser with extension filtering.
  • Style system: Lipgloss-inspired Style with width/height constraints, Align/AlignVertical, inline mode, AdaptiveColor, Border variants; joinHorizontal(), joinVertical(), place() layout helpers.
  • EchoMode (normal / password / none) and suggestions (tab-completion) on TextInputModel.
  • ValidationFailedMsg + validate callback on TextInputModel.
  • tickWithId(Duration, Object): tick Cmd with routing ID for composable timers.
  • batch() / sequence(): concurrent vs. ordered command scheduling.
  • Mouse support: MouseMode.cellMotion / allMotion; MouseClickMsg, MouseMotionMsg, MouseWheelMsg.
  • Cursor control: View.cursor with CursorShape (block / underline / bar) and blink flag.
  • Focus reporting: View.reportFocus, FocusMsg, BlurMsg.
  • Window title: View.windowTitle OSC sequence.
  • 41 examples: complete port of the Bubbletea example gallery.

Performance improvements #

  • Enter / LF key fix: 0x0a (LF / \n) was silently decoded as ctrl+j and dropped by all components. It is now correctly mapped to KeyCode.enter, matching Linux/WSL terminal behaviour.
  • Batch render loop: messages are now drained without re-rendering between each one; a single render fires per batch. Eliminates up to 16 ms of FPS-throttle lag per key press.
  • Unawaited commands: runCmd is now fire-and-forget so command results arrive as the next queued message without blocking key event processing.
  • Deferred capability queries: CSI ?2026$y (synchronized-updates query) and OSC 11 (background-color query) are sent after the first rendered frame, so the initial visible output is not delayed.
  • Kernel snapshot build: tool/build.sh --kernel compiles examples to .dill kernel snapshots (~550 ms startup vs ~1 050 ms JIT source on WSL2).
  • Makefile: added targets for test, analyze, run, run-fast, kernels, bench, gifs, new-example, and more.
  • 48 GIFs re-recorded from kernel snapshots for faster, cleaner recordings.

Bug fixes #

  • Terminal not restored on quit: added _output.writeln() + await _output.flush() in the finally block so ANSI reset sequences (show cursor, exit alt-screen) are flushed to the terminal before the shell regains control.
  • Terminal hang on exit: awaiting the stdin subscription cancel in the shutdown path so the Dart event loop is fully released before the process exits. Previously the unawaited cancel could leave stdin holding the event loop open, requiring a manual Ctrl-C to regain the shell prompt.

Breaking changes from 0.1.0 #

  • Legacy Go-style uppercase API aliases removed (Batch, Sequence, Quit, Println, WithInput, etc.) — use canonical lowercase equivalents.
  • request* commands now emit real terminal protocol queries; decoder responses feed back as typed Msg values.
  • ListModel renamed to SelectListModel.

0.1.0 #

  • Initial release: Program runtime (Elm-style Model, Msg, Cmd, Batch).
  • Terminal integration via dart_console (raw keys, window size, resize).
  • Components: ListModel, TextInputModel, SpinnerModel, ProgressModel.
  • Optional prompts helpers: select, confirm, input.
  • Example: shopping list (Bubble Tea tutorial port).
5
likes
160
points
6.82k
downloads

Documentation

API reference

Publisher

verified publisherhashstudios.dev

Weekly Downloads

Elm-style terminal UI framework for Dart — Model–Update–View, async commands, 27+ components, Lipgloss-inspired styling, spring animation, and gum-style helpers.

Repository (GitHub)
View/report issues

Topics

#terminal #tui #cli #elm #bubbletea

License

MIT (license)

Dependencies

characters, ffi, path

More

Packages that depend on dart_tui