Terminal User Interface framework for Java that ports the Charmbracelet ecosystem (Bubble Tea, Bubbles, Lipgloss, Harmonica) from Go, enabling developers to build interactive CLI applications using The Elm Architecture pattern.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
TUI4J is a comprehensive Terminal User Interface framework for Java that ports the Charmbracelet ecosystem from Go to Java. It enables developers to build interactive, event-driven command-line applications using The Elm Architecture pattern with Model-Update-View, along with pre-built UI components, styling capabilities, and physics-based animations.
<dependency>
<groupId>com.williamcallahan</groupId>
<artifactId>tui4j</artifactId>
<version>0.3.3</version>
</dependency>Gradle:
implementation 'com.williamcallahan:tui4j:0.3.3'// Core Bubble Tea framework
import com.williamcallahan.tui4j.compat.bubbletea.Program;
import com.williamcallahan.tui4j.compat.bubbletea.Model;
import com.williamcallahan.tui4j.compat.bubbletea.Command;
import com.williamcallahan.tui4j.compat.bubbletea.Message;
import com.williamcallahan.tui4j.compat.bubbletea.UpdateResult;
// Input handling
import com.williamcallahan.tui4j.compat.bubbletea.KeyPressMessage;
import com.williamcallahan.tui4j.compat.bubbletea.input.key.Key;
import com.williamcallahan.tui4j.compat.bubbletea.input.key.KeyType;
import com.williamcallahan.tui4j.compat.bubbletea.input.MouseMessage;
// Styling (Lipgloss)
import com.williamcallahan.tui4j.compat.lipgloss.Style;
import com.williamcallahan.tui4j.compat.lipgloss.Renderer;
import com.williamcallahan.tui4j.compat.lipgloss.color.Color;
// UI Components (Bubbles)
import com.williamcallahan.tui4j.compat.bubbles.list.List;
import com.williamcallahan.tui4j.compat.bubbles.textinput.TextInput;
import com.williamcallahan.tui4j.compat.bubbles.spinner.Spinner;import com.williamcallahan.tui4j.compat.bubbletea.*;
import com.williamcallahan.tui4j.compat.bubbletea.input.key.*;
// Define your model implementing the Model interface
class MyModel implements Model {
private final String message;
public MyModel(String message) {
this.message = message;
}
@Override
public Command init() {
// Return initial command (often Command.none())
return Command.none();
}
@Override
public UpdateResult<? extends Model> update(Message msg) {
// Handle keyboard input
if (msg instanceof KeyPressMessage keyMsg) {
Key key = keyMsg.key();
// Quit on 'q' or Ctrl+C
if (key.type() == KeyType.KeyRunes &&
key.runes().length > 0 && key.runes()[0] == 'q') {
return UpdateResult.from(this, Command.quit());
}
if (key.type() == KeyType.KeyCtrlC) {
return UpdateResult.from(this, Command.quit());
}
}
// Return unchanged model
return UpdateResult.from(this);
}
@Override
public String view() {
// Render the UI
return message + "\n\nPress 'q' to quit.";
}
}
// Run the program
public class Main {
public static void main(String[] args) {
Model initialModel = new MyModel("Hello, TUI4J!");
Program program = new Program(initialModel);
program.run();
}
}TUI4J follows The Elm Architecture pattern, which structures applications around three core concepts:
This architecture enables:
The Program manages an event loop that:
init() on the model to get initial state and commandsupdate(), which returns new model and commandsview() to render the current stateThe core event-driven framework based on The Elm Architecture. Provides the Program runner, Model interface, Command system for side effects, and Message-based communication.
// Program - Main entry point
class Program {
Program(Model initialModel);
Program(Model initialModel, ProgramOption... options);
void run();
Model runWithFinalModel();
void send(Message msg);
boolean isRunning();
}
// Model - Application state interface
interface Model {
Command init();
UpdateResult<? extends Model> update(Message msg);
String view();
}
// Command - Side effects
interface Command {
Message execute();
// Factory methods
static Command none();
static Command quit();
static Command batch(Command... commands);
static Command tick(Duration duration, Function<Object, Message> fn);
// ... many more factory methods
}
// UpdateResult - Result of update
record UpdateResult<M extends Model>(M model, Command command) {
static <M extends Model> UpdateResult<M> from(M model, Command cmd);
static <M extends Model> UpdateResult<M> from(M model);
}Keyboard and mouse input processing with support for all key types, modifiers, mouse buttons, and motion tracking.
// Key input
class KeyPressMessage implements Message {
Key key();
}
record Key(KeyType type, char[] runes, boolean alt) { }
enum KeyType {
KeyCtrlC, KeyCtrlD, KeyEnter, KeyEsc, KeySpace, KeyTab,
KeyUp, KeyDown, KeyLeft, KeyRight, KeyRunes,
// ... many more key types
}
// Mouse input
class MouseMessage implements Message {
int column();
int row();
MouseButton getButton();
MouseAction getAction();
boolean isShift();
boolean isAlt();
boolean isCtrl();
}
enum MouseButton {
MouseButtonLeft, MouseButtonRight, MouseButtonMiddle,
MouseButtonWheelUp, MouseButtonWheelDown
}
enum MouseAction {
MouseActionPress, MouseActionRelease, MouseActionMotion
}Comprehensive styling system for colors, text attributes, borders, padding, margins, and layout. Provides a fluent API for building styled text with precise control over appearance.
// Style builder
class Style {
static Style newStyle();
// Colors
Style foreground(TerminalColor color);
Style background(TerminalColor color);
// Text attributes
Style bold();
Style italic();
Style underline();
// Layout
Style width(int width);
Style height(int height);
Style padding(int top, int right, int bottom, int left);
Style margin(int top, int right, int bottom, int left);
Style border(Border border);
Style alignHorizontal(Position pos);
Style alignVertical(Position pos);
// Rendering
String render(String content);
}
// Color factory
class Color {
static TerminalColor color(String hex);
static TerminalColor color(int r, int g, int b);
}Pre-built, reusable UI components including lists, text inputs, tables, spinners, progress bars, viewports, and more. All components implement the Model interface and can be embedded in your application.
// List component
class List implements Model {
List(Item[] items, int width, int height);
Item selectedItem();
int selectedIndex();
}
// Text input component
class TextInput implements Model {
TextInput();
String value();
void focus();
void blur();
}
// Table component
class Table implements Model {
static Table create();
Table columns(Column... columns);
Table rows(Row... rows);
Row selectedRow();
}
// Spinner component
class Spinner implements Model {
Spinner(SpinnerType type);
}
// Progress bar component
class Progress implements Model {
Progress();
double percent();
SetPercentMessage setPercent(double percent);
}
// Viewport (scrollable content)
class Viewport implements Model {
Viewport(String content, int height);
void lineDown();
void lineUp();
void pageDown();
void pageUp();
}
// Textarea (multi-line editor)
class Textarea implements Model {
Textarea();
String value();
void setValue(String value);
}Physics-based spring animation system for smooth, natural motion. Harmonica provides spring dynamics with configurable damping and frequency.
class Spring {
static Spring newSpring(double deltaTime, double angularFrequency, double dampingRatio);
double update(double position, double velocity, double target);
double velocity();
}ANSI-aware text processing including width calculation, wrapping, truncation, and grapheme cluster handling.
// Text width calculation
class TextWidth {
static int width(String text);
}
// Text wrapping
class TextWrapper {
static String wrap(String text, int width);
static String wordWrap(String text, int width);
}
// Text truncation
class Truncate {
static String truncate(String text, int width);
static String truncate(String text, int width, String tail);
}Components can be embedded in your model and delegated to:
class MyModel implements Model {
private final TextInput input;
private final Spinner spinner;
@Override
public UpdateResult<? extends Model> update(Message msg) {
// Delegate to component
UpdateResult<TextInput> inputResult = input.update(msg);
// Create new model with updated component
return UpdateResult.from(
new MyModel(inputResult.model(), spinner),
inputResult.command()
);
}
@Override
public String view() {
return input.view() + "\n" + spinner.view();
}
}Commands represent side effects and return messages:
@Override
public UpdateResult<? extends Model> update(Message msg) {
if (msg instanceof KeyPressMessage) {
// Execute async operation
Command cmd = Command.tick(
Duration.ofSeconds(1),
id -> new TimeoutMessage()
);
return UpdateResult.from(this, cmd);
}
return UpdateResult.from(this);
}Define custom messages for your application:
record TimeoutMessage() implements Message { }
record DataLoadedMessage(String data) implements Message { }
@Override
public UpdateResult<? extends Model> update(Message msg) {
if (msg instanceof TimeoutMessage) {
// Handle timeout
} else if (msg instanceof DataLoadedMessage data) {
// Handle loaded data
}
return UpdateResult.from(this);
}Configure the program with options:
Program program = new Program(
initialModel,
Program.withAltScreen(), // Use alternate screen buffer
Program.withMouseCellMotion(), // Enable mouse tracking
Program.withReportFocus() // Report focus/blur events
);
program.run();Errors are propagated through the message system:
// ErrorMessage is sent when commands fail
if (msg instanceof ErrorMessage error) {
Throwable err = error.error();
// Handle error
return UpdateResult.from(this.withError(err.getMessage()));
}highPerformanceRendering in Viewport for large contentCommand.batch() to reduce overheadTUI4J is designed for single-threaded use. The event loop runs on one thread. To send messages from other threads, use Program.send():
// From another thread
program.send(new CustomMessage());