Skip to content
Blog

Building Modern Terminal UIs in Dart: dart_tui vs. commander_ui

A comprehensive guide to building interactive terminal interfaces in Dart using dart_tui and commander_ui frameworks

Published on September 12, 2026

AI Assistant

Building Modern Terminal UIs in Dart: dart_tui vs. commander_ui

Command-line interfaces (CLIs) have evolved far beyond basic text logs and simple prompts. Modern developers demand rich, interactive, and responsive terminal interfaces directly in standard terminals. While technologies like Rust (Ratatui) and Go (Bubbletea) have popularized Terminal User Interfaces (TUIs), Dart has established a robust TUI ecosystem. This guide explores the two leading Dart frameworks: dart_tui and commander_ui.

1. Architectural Deep-Dive

dart_tui: Elm-Style / Model-Update-View Architecture

dart_tui closely aligns with the architecture popularized by Elm and Go’s Bubbletea. It enforces strict unidirectional data flow, ensuring that state transitions remain predictable regardless of application scale.

  • Model: Represents the entire application state in an immutable structure.
  • Update: A pure reducer function that receives incoming messages (keyboard inputs, window events, or async ticks) and produces an updated Model alongside optional Commands.
  • View: A declarative function converting the current Model state into structural layout widgets for screen rendering.

commander_ui: Immediate-Mode and Dual Paradigm

commander_ui offers a hybrid model tailored for rapid developer ergonomics. It separates lightweight prompt interactions from stateful full-screen terminal views.

  • Prompt Mode: Streamlines single-line or multi-choice interactive questions into standard CLI scripts.
  • TUI Mode: Employs an immediate-mode design pattern where UI state is updated directly in reactive loops, minimizing boilerplate code for simpler interactive applications.

2. Code Implementations

Option A: Interactive Task Dashboard using dart_tui

import 'package:dart_tui/dart_tui.dart';

// 1. Application Model State
class TaskModel {
  final List<String> tasks;
  final int selectedIndex;
  final bool isRunning;

  TaskModel({
    required this.tasks,
    required this.selectedIndex,
    this.isRunning = true,
  });

  TaskModel copyWith({List<String>? tasks, int? selectedIndex, bool? isRunning}) {
    return TaskModel(
      tasks: tasks ?? this.tasks,
      selectedIndex: selectedIndex ?? this.selectedIndex,
      isRunning: isRunning ?? this.isRunning,
    );
  }
}

// 2. Event Messages
abstract class Msg {}
class MoveUpMsg extends Msg {}
class MoveDownMsg extends Msg {}
class QuitMsg extends Msg {}

// 3. Update Reducer
TaskModel update(Msg msg, TaskModel model) {
  if (msg is MoveUpMsg) {
    final newIdx = (model.selectedIndex > 0) ? model.selectedIndex - 1 : 0;
    return model.copyWith(selectedIndex: newIdx);
  } else if (msg is MoveDownMsg) {
    final maxIdx = model.tasks.length - 1;
    final newIdx = (model.selectedIndex < maxIdx) ? model.selectedIndex + 1 : maxIdx;
    return model.copyWith(selectedIndex: newIdx);
  } else if (msg is QuitMsg) {
    return model.copyWith(isRunning: false);
  }
  return model;
}

// 4. Declarative View Renderer
Widget view(TaskModel model) {
  final items = model.tasks.asMap().entries.map((entry) {
    final idx = entry.key;
    final title = entry.value;
    final prefix = (idx == model.selectedIndex) ? '> [x] ' : '  [ ] ';
    final style = (idx == model.selectedIndex) 
        ? Style(color: Color.cyan, bold: true)
        : Style(color: Color.white);
    
    return Text('$prefix$title', style: style);
  }).toList();

  return Column(
    children: [
      Header(text: '--- DART_TUI TASK DASHBOARD ---', style: Style(color: Color.blue)),
      ...items,
      Divider(),
      Text('Press [k/Up] Prev | [j/Down] Next | [q] Quit', style: Style(color: Color.gray)),
    ],
  );
}

void main() {
  final initialModel = TaskModel(
    tasks: ['Setup Firebase Hosting', 'Refactor ADK Engine', 'Compile Rust Bindings'],
    selectedIndex: 0,
  );

  TuiApp(
    initialModel: initialModel,
    update: update,
    view: view,
    keyMapper: (key) {
      if (key.char == 'q') return QuitMsg();
      if (key.char == 'k' || key.code == KeyCode.up) return MoveUpMsg();
      if (key.char == 'j' || key.code == KeyCode.down) return MoveDownMsg();
      return null;
    },
  ).run();
}

Option B: Rapid CLI Dashboard using commander_ui

import 'package:commander_ui/commander_ui.dart';

void main() async {
  final ui = CommanderUI();

  ui.writeHeader('COMMANDER_UI DEPLOYMENT WIZARD');

  // Interactive selection prompt
  final environment = await ui.select(
    message: 'Select Deployment Target:',
    options: ['Staging (GCP)', 'Production (AWS)', 'Local Emulator'],
  );

  ui.info('Selected Environment: $environment');

  // Interactive Progress Tracker
  final progress = ui.progressSpinner(message: 'Building Container Image...');
  progress.start();
  await Future.delayed(Duration(seconds: 2));
  progress.update('Pushing Artifacts to Registry...');
  await Future.delayed(Duration(seconds: 2));
  progress.stop('Deployment Complete!');

  // Render Dynamic Stats Panel
  ui.renderPanel(
    title: 'System Status',
    content: 'Status: ONLINE\nActive Agents: 12\nCPU Load: 14.2%',
    borderColor: ConsoleColor.green,
  );
}

3. Feature Matrix Comparison

Feature / Dimensiondart_tuicommander_ui
Architecture PatternElm / Redux (Model-Update-View)Immediate Mode / Procedural Prompts
State ManagementStrict, unidirectional immutable stateImperative and stateful callbacks
Best ForComplex dashboards, full-screen apps, Vim-like toolsCLI Wizards, setup scripts, rapid status displays
Component SetTables, TextAreas, Tabs, Modals, ListsPrompts, Spinners, Simple Panels, Sparklines
Keybinding FlexibilityHigh (Custom event-to-msg mapping)Standard CLI input handlers

4. Architectural Recommendation

Recommendation: If you are building an ongoing enterprise terminal application, system monitoring tool, or multi-tab workspace, choose dart_tui for its maintainable state separation. For quick developer tooling, scaffolding generators, or deployment wizards, select commander_ui for its minimal boilerplate and rapid iteration speed.

5. Conclusion

The Dart package ecosystem now offers mature tools for terminal interface development. Whether building full-featured desktop-class terminal software using Elm architecture or authoring lightweight CLI scripts with interactive prompts, both dart_tui and commander_ui provide robust solutions for modern Dart backend and systems developers.