TamboUI provides a comprehensive set of widgets for building terminal UIs.
Widget Overview
| Widget | Type | Description |
|---|---|---|
Stateless |
Container with borders, titles, and padding |
|
Stateless |
Multi-line text with wrapping and alignment |
|
Stateful |
Scrollable list with selection |
|
Stateful |
Grid with rows, columns, and selection |
|
Stateful |
Tab bar with selection |
|
Stateless |
Progress bar with percentage |
|
Stateless |
Horizontal line progress indicator |
|
Stateless |
Mini line chart for data series |
|
Stateless |
Vertical bar chart |
|
Stateless |
Line and scatter plots with axes |
|
Stateless |
Drawing surface with shapes |
|
Stateless |
Monthly calendar with date styling |
|
Stateful |
Animated loading indicator |
|
Stateful |
Visual scroll position indicator |
|
Stateful |
Single-line text input |
|
Stateful |
Multi-line text input |
|
Stateful |
Toggleable checkbox |
|
Stateful |
On/off toggle switch |
|
Stateful |
Inline select with navigation indicators |
|
Stateful |
Hierarchical tree view with expand/collapse |
|
Stateful |
Animated wave brightness text effect |
|
Stateless |
Renders the Tamboui logo |
|
Stateless |
Displays error information with stack trace |
|
Stateless |
Clears an area (for widget layering) |
Container Widgets
Block
A Block is the most fundamental container widget. It can have borders, titles, and padding.
Block block = Block.builder()
.title("My Title")
.borders(Borders.ALL)
.borderType(BorderType.ROUNDED)
.borderStyle(Style.EMPTY.fg(Color.CYAN))
.padding(new Padding(1, 2, 1, 2)) // top, right, bottom, left
.build();
// Render the block
block.render(area, buffer);
// Get the inner area (after borders and padding)
Rect inner = block.inner(area);
Border types:
-
BorderType.PLAIN- Simple lines (─,│) -
BorderType.ROUNDED- Rounded corners (╭,╮,╰,╯) -
BorderType.DOUBLE- Double lines (═,║) -
BorderType.THICK- Thick lines (━,┃)
Clear
Clears an area by filling it with spaces. Useful before rendering overlays:
Clear.INSTANCE.render(dialogArea, buffer);
// Then render your dialog on top
For layout widgets (Columns, Grid, Dock, Stack, Flow), see Layouts Reference.
Text Widgets
Paragraph
Displays multi-line text with wrapping and alignment:
Paragraph paragraph = Paragraph.builder()
.text(Text.from("This is a paragraph of text that can wrap across multiple lines."))
.overflow(Overflow.WRAP_WORD)
.alignment(Alignment.LEFT)
.block(Block.builder()
.title("Description")
.borders(Borders.ALL)
.build())
.build();
paragraph.render(area, buffer);
Wrap modes:
-
Wrap.NONE- No wrapping, text is clipped -
Wrap.CHARACTER- Wrap at any character -
Wrap.WORD- Wrap at word boundaries
Selection Widgets
ListWidget
A low-level scrollable list widget with selection. This is part of the widgets layer and requires manual state management with ListState.
For Toolkit DSL users: Use ListElement instead (via list() factory method). It manages state internally and accepts any StyledElement as items. See ListWidget vs ListElement.
|
// Create items
List<ListItem> items = List.of(
ListItem.from("First item"),
ListItem.from("Second item"),
ListItem.from("Styled item").style(Style.EMPTY.bold())
);
// Create state
ListState state = new ListState();
state.select(0); // Select first item
// Build and render
ListWidget list = ListWidget.builder()
.items(items)
.highlightStyle(Style.EMPTY.reversed())
.highlightSymbol(">> ")
.block(Block.builder()
.title("Select an Item")
.borders(Borders.ALL)
.build())
.build();
list.render(area, buffer, state);
State methods:
-
state.select(index)- Select specific index -
state.selectNext(totalItems)- Move selection down -
state.selectPrevious()- Move selection up -
state.selectFirst()- Jump to first item -
state.selectLast(totalItems)- Jump to last item
ListWidget vs ListElement
TamboUI provides two list implementations at different API levels:
| Feature | ListWidget (widgets layer) | ListElement (toolkit layer) |
|---|---|---|
State management |
External |
Internal state, no external state needed |
Item types |
|
Any |
Usage |
Direct widget rendering |
Toolkit DSL fluent API |
Best for |
Custom TuiRunner apps, fine-grained control |
ToolkitApp, declarative UIs |
ListWidget is for low-level control when you need to manage state externally or are building custom rendering with TuiRunner.
ListElement is for Toolkit DSL users who want:
-
Internal state management (no
ListStateneeded) -
Rich content (any
StyledElementas items, not just text) -
Automatic event handling (UP/DOWN navigation built-in)
-
Seamless integration with other toolkit elements
// Toolkit DSL - ListElement with rich content
list()
.add(row(text("*").yellow(), text(" Featured Item")))
.add(text("Plain Item"))
.add(panel("Nested Panel", text("Content")).rounded())
.highlightColor(Color.CYAN)
.autoScroll();
Table
A data table with rows and columns:
// Create table
Table table = Table.builder()
.header(Row.from("Name", "Age", "City"))
.rows(
Row.from("Alice", "30", "NYC"),
Row.from("Bob", "25", "LA"),
Row.from("Charlie", "35", "Chicago")
)
.widths(
Constraint.percentage(40),
Constraint.length(10),
Constraint.fill()
)
.highlightStyle(Style.EMPTY.bg(Color.DARK_GRAY))
.block(Block.builder()
.title("Users")
.borders(Borders.ALL)
.build())
.build();
// Create and use state
TableState state = new TableState();
state.select(0);
table.render(area, buffer, state);
Tabs
A tab bar for navigation:
Tabs tabs = Tabs.builder()
.titles("Home", "Settings", "About")
.highlightStyle(Style.EMPTY.bold().fg(Color.CYAN))
.divider(" | ")
.block(Block.builder()
.borders(Borders.BOTTOM_ONLY)
.build())
.build();
TabsState state = new TabsState();
state.select(0);
tabs.render(area, buffer, state);
Tree
A hierarchical tree view with expand/collapse, keyboard navigation, and support for custom node rendering. Use TreeElement (toolkit layer) with TreeNode for the data model.
// Create tree nodes with data model
TreeNode<FileInfo> src = TreeNode.of("src", new FileInfo("src", FileType.DIRECTORY))
.add(TreeNode.of("main", new FileInfo("main", FileType.DIRECTORY))
.add(TreeNode.of("App.java", new FileInfo("App.java", FileType.JAVA, 2048)).leaf())
.expanded())
.expanded();
// Create tree with custom node renderer
tree(src, docs, build)
.title("Project Files")
.rounded()
.highlightColor(Color.CYAN)
.scrollbar()
.nodeRenderer(node -> row(
text(node.data().icon() + " "),
text(node.label()).bold(),
spacer(),
text(node.data().formattedSize()).dim()
));
Model/View Separation
TreeElement encourages clean separation between data and presentation:
Model (TreeNode + Data): Contains only data, no rendering logic.
// Pure data model - no view concerns
record FileData(String name, FileType type, long size, Status status) {
String icon() { /* data-derived */ return ""; }
String formattedSize() { /* data formatting */ return ""; }
}
FileData data = new FileData("App.java", FileType.JAVA, 2048, Status.OK);
// TreeNode holds the data
TreeNode<FileData> node = TreeNode.of("App.java", data).leaf();
View (nodeRenderer): Transforms data into styled elements.
// Rendering logic separated from model
tree(node).nodeRenderer(n -> {
var info = n.data();
return row(
text(info.icon() + " "),
text(info.name()).fg(info.statusColor()),
spacer(),
text(info.formattedSize()).dim()
);
});
This separation allows:
-
Reusable models: Same
TreeNode<T>data can be rendered differently in different contexts -
Testable logic: Data transformations can be unit tested without UI
-
Flexible presentation: Change rendering without modifying data structures
Keyboard Navigation
| Key | Action |
|---|---|
↑/↓ |
Move selection up/down |
→ |
Expand node or move to first child |
← |
Collapse node or move to parent |
Enter/Space |
Toggle expand/collapse |
Home/End |
Jump to first/last visible node |
Page Up/Down |
Scroll by viewport height |
Lazy Loading
Load children on-demand for large trees:
TreeNode.of("Large Directory", dirInfo)
.childrenLoader(() -> loadChildrenFromDisk(dirInfo.name()));
The loader is called once when the node is first expanded.
CSS Styling
Style trees with CSS, including the :selected pseudo-class:
/* Guide characters */
TreeElement-guide {
color: #45475a;
}
/* Selected node styling */
TreeElement-node:selected {
color: #89dceb;
background: #313244;
bold: true;
}
/* Scrollbar */
TreeElement-scrollbar-thumb {
color: #89b4fa;
}
CSS selectors:
-
TreeElement- the container -
TreeElement-node- each tree node -
TreeElement-node:selected- the selected node -
TreeElement-guide- guide/branch characters -
TreeElement-scrollbar-thumb- scrollbar thumb -
TreeElement-scrollbar-track- scrollbar track
Data Visualization
Gauge
A progress bar showing completion percentage:
Gauge gauge = Gauge.builder()
.ratio(0.75) // 75% complete
.label("Loading...")
.gaugeStyle(Style.EMPTY.fg(Color.GREEN))
.block(Block.builder()
.title("Progress")
.borders(Borders.ALL)
.build())
.build();
gauge.render(area, buffer);
LineGauge
A compact single-line progress indicator:
LineGauge lineGauge = LineGauge.builder()
.ratio(0.5)
.label("50%")
.lineSet(LineGauge.THICK)
.filledStyle(Style.EMPTY.fg(Color.CYAN))
.unfilledStyle(Style.EMPTY.fg(Color.DARK_GRAY))
.build();
lineGauge.render(area, buffer);
Sparkline
A mini chart showing data trends:
int[] data = {1, 4, 2, 8, 5, 7, 3, 6};
Sparkline sparkline = Sparkline.builder()
.data(data)
.foreground(Color.CYAN)
.block(Block.builder()
.title("CPU Usage")
.borders(Borders.ALL)
.build())
.build();
sparkline.render(area, buffer);
BarChart
Vertical bar chart for comparing values:
BarChart barChart = BarChart.builder()
.data(BarGroup.of(
Bar.of(80, "Mon"),
Bar.of(95, "Tue"),
Bar.of(60, "Wed"),
Bar.of(75, "Thu"),
Bar.of(90, "Fri")
))
.barWidth(5)
.barGap(1)
.barColor(Color.GREEN)
.labelColor(Color.DARK_GRAY)
.block(Block.builder()
.title("Weekly Sales")
.borders(Borders.ALL)
.build())
.build();
barChart.render(area, buffer);
Chart
Line and scatter plots with axes:
// Create datasets
Dataset dataset1 = Dataset.builder()
.name("Series A")
.data(new double[][] {
{0, 0},
{1, 2},
{2, 1},
{3, 4},
{4, 3}
})
.marker(Dataset.Marker.BRAILLE)
.style(Style.EMPTY.fg(Color.CYAN))
.build();
// Build chart
Chart chart = Chart.builder()
.datasets(dataset1)
.xAxis(Axis.builder()
.title("X Axis")
.bounds(0, 5)
.build())
.yAxis(Axis.builder()
.title("Y Axis")
.bounds(0, 5)
.build())
.block(Block.builder()
.title("Line Chart")
.borders(Borders.ALL)
.build())
.build();
chart.render(area, buffer);
Marker types:
-
Marker.DOT- Single dots -
Marker.BRAILLE- Braille patterns for higher resolution -
Marker.BLOCK- Block characters
Canvas
A drawing surface for custom graphics:
Canvas canvas = Canvas.builder()
.xBounds(-10, 10)
.yBounds(-10, 10)
.marker(Marker.BRAILLE)
.paint(ctx -> {
// Draw shapes
ctx.draw(new Circle(0, 0, 5, Color.RED));
ctx.draw(new Line(-5, -5, 5, 5, Color.GREEN));
ctx.draw(new Rectangle(-3, -3, 6, 6, Color.BLUE));
})
.block(Block.builder()
.title("Drawing")
.borders(Borders.ALL)
.build())
.build();
canvas.render(area, buffer);
Available shapes:
-
Circle- Circle with center and radius -
Line- Line between two points -
Rectangle- Rectangle with position and size -
Points- Collection of individual points
Calendar
A monthly calendar widget:
CalendarEventStore events = CalendarEventStore.today(Style.EMPTY.bold().fg(Color.CYAN));
Monthly calendar = Monthly.of(LocalDate.now(), events)
.showMonthHeader(Style.EMPTY.bold())
.showWeekdaysHeader(Style.EMPTY.fg(Color.CYAN))
.showSurrounding(Style.EMPTY.dim())
.block(Block.bordered());
calendar.render(area, buffer);
Animated Widgets
Spinner
An animated loading indicator that cycles through frame characters:
// Create spinner with built-in style
Spinner spinner = Spinner.builder()
.spinnerStyle(SpinnerStyle.DOTS)
.style(Style.EMPTY.fg(Color.CYAN))
.build();
// Or with custom frames
Spinner spinnerCustom = Spinner.builder()
.frames("*", "+", "x", "+")
.build();
// Or with a custom frame set
Spinner spinnerFrameSet = Spinner.builder()
.frameSet(SpinnerFrameSet.of("a", "b", "c", "d"))
.build();
// Create state and render
SpinnerState state = new SpinnerState();
spinner.render(area, buffer, state);
// Advance state on each tick
state.advance();
Built-in styles:
-
SpinnerStyle.DOTS- Braille dot pattern -
SpinnerStyle.LINE- Classic-\|/ -
SpinnerStyle.ARC- Quarter-circle characters -
SpinnerStyle.CIRCLE- Clock-position circle -
SpinnerStyle.BOUNCING_BAR- Bouncing bar[=== ] -
SpinnerStyle.TOGGLE- Two-state toggle -
SpinnerStyle.GAUGE- Horizontal block fill (▏▎▍▌▋▊▉█) -
SpinnerStyle.VERTICAL_GAUGE- Vertical bar growth (▁▂▃▄▅▆▇█) -
SpinnerStyle.ARROWS- Rotating arrow directions -
SpinnerStyle.CLOCK- Clock face emoji (🕐🕑…) -
SpinnerStyle.MOON- Moon phase emoji (🌑🌒…) -
SpinnerStyle.SQUARE_CORNERS- Rotating square corners -
SpinnerStyle.GROWING_DOTS- Braille dots filling -
SpinnerStyle.BOUNCING_BALL- Bouncing ball in braille
CSS Properties
Spinner style and frames can be configured via CSS:
/* Use a predefined style */
.loading-spinner {
spinner-style: line;
color: cyan;
}
/* Use custom animation frames */
.custom-spinner {
spinner-frames: "⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧";
}
/* spinner-frames overrides spinner-style when both are set */
.fancy-spinner {
spinner-style: dots;
spinner-frames: "*" "+" "x" "+"; /* This takes precedence */
}
Programmatic styles set via spinnerStyle(), frameSet(), or frames() take precedence over CSS styles.
|
For Toolkit DSL users: Use SpinnerElement via spinner() factory methods. It manages state internally and advances automatically on each render. See Using Widgets with Toolkit DSL.
|
WaveText
An animated text widget with a wave brightness effect:
// Dark shadow moving through bright text (default)
WaveText wave = WaveText.builder()
.text("Processing...")
.color(Color.CYAN)
.build();
// Bright peak moving through dim text (inverted)
WaveText waveInverted = WaveText.builder()
.text("Loading...")
.color(Color.YELLOW)
.inverted(true)
.build();
// Back-and-forth oscillation instead of looping
WaveText waveOscillate = WaveText.builder()
.text("Thinking...")
.mode(WaveText.Mode.OSCILLATE)
.build();
// Multiple peaks for dynamic effect
WaveText waveMulti = WaveText.builder()
.text("Working hard...")
.color(Color.GREEN)
.peakCount(2)
.peakWidth(5)
.speed(1.5)
.build();
// Create state and render
WaveTextState state = new WaveTextState();
wave.render(area, buffer, state);
// Advance animation each frame
state.advance();
Configuration options:
-
color(Color)- Base color of the text -
dimFactor(double)- Brightness of dim portion (0.0-1.0, default 0.3) -
peakWidth(int)- Width of the wave peak in characters (default 3) -
peakCount(int)- Number of peaks in the wave (default 1) -
speed(double)- Animation speed multiplier (default 1.0) -
mode(Mode)-LOOP(continuous) orOSCILLATE(back-and-forth) -
inverted(boolean)- If true, bright peak on dim text; if false, dark shadow on bright text
CSS properties:
-
color- Base text color -
wave-dim-factor- Brightness of dim portion -
wave-peak-width- Width of wave peak -
wave-peak-count- Number of peaks -
wave-speed- Animation speed multiplier
Input Widgets
TextInput
A single-line text input field:
// Create state
TextInputState state = new TextInputState();
// Or with initial value
TextInputState stateWithValue = new TextInputState("initial text");
// Build widget
TextInput textInput = TextInput.builder()
.placeholder("Enter your name...")
.cursorStyle(Style.EMPTY.reversed())
.block(Block.builder()
.title("Name")
.borders(Borders.ALL)
.build())
.build();
textInput.render(area, buffer, state);
// Handle key events
if (event.code() == KeyCode.CHAR) {
state.insert(event.character());
} else if (event.code() == KeyCode.BACKSPACE) {
state.deleteBackward();
} else if (event.code() == KeyCode.DELETE) {
state.deleteForward();
} else if (event.code() == KeyCode.LEFT) {
state.moveCursorLeft();
} else if (event.code() == KeyCode.RIGHT) {
state.moveCursorRight();
}
State methods:
-
state.text()- Get current text -
state.setText(text)- Set text -
state.insert(char)- Insert character at cursor -
state.deleteBackward()- Backspace -
state.deleteForward()- Delete -
state.moveCursorLeft()/moveCursorRight()- Move cursor -
state.clear()- Clear all text
TextArea
A multi-line text input field with scrolling and optional line numbers:
// Create state
TextAreaState state = new TextAreaState();
// Or with initial content
TextAreaState stateWithContent = new TextAreaState("Line 1\nLine 2\nLine 3");
// Build widget
TextArea textArea = TextArea.builder()
.placeholder("Enter text...")
.showLineNumbers(true)
.cursorStyle(Style.EMPTY.reversed())
.lineNumberStyle(Style.EMPTY.dim())
.block(Block.builder()
.title("Editor")
.borders(Borders.ALL)
.build())
.build();
// Render with cursor visible
textArea.renderWithCursor(area, buffer, state, frame);
// Handle key events
if (event.code() == KeyCode.CHAR) {
state.insert(event.character());
} else if (event.code() == KeyCode.ENTER) {
state.insert('\n');
} else if (event.code() == KeyCode.BACKSPACE) {
state.deleteBackward();
} else if (event.code() == KeyCode.UP) {
state.moveCursorUp();
} else if (event.code() == KeyCode.DOWN) {
state.moveCursorDown();
}
State methods:
-
state.text()- Get full text content -
state.setText(text)- Set text content -
state.lineCount()- Get number of lines -
state.getLine(row)- Get text of specific line -
state.insert(char)/state.insert(string)- Insert at cursor -
state.deleteBackward()/state.deleteForward()- Delete characters -
state.moveCursorUp()/moveCursorDown()/moveCursorLeft()/moveCursorRight()- Move cursor -
state.moveCursorToLineStart()/moveCursorToLineEnd()- Jump to line boundaries -
state.moveCursorToStart()/moveCursorToEnd()- Jump to document boundaries -
state.scrollUp(amount)/state.scrollDown(amount, visibleRows)- Manual scrolling -
state.clear()- Clear all content
CSS properties:
-
cursor-color- Cursor highlight color -
placeholder-color- Placeholder text color -
line-number-color- Line number gutter color
Checkbox
A toggleable checkbox widget displaying checked/unchecked state:
// Create state
CheckboxState state = new CheckboxState(); // unchecked
CheckboxState stateChecked = new CheckboxState(true); // checked
// Build widget with default style [x] / [ ]
Checkbox checkbox = Checkbox.builder().build();
// Custom symbols
Checkbox checkboxCustom = Checkbox.builder()
.checkedSymbol("[v]")
.uncheckedSymbol("[ ]")
.checkedColor(Color.GREEN)
.uncheckedColor(Color.DARK_GRAY)
.build();
checkbox.render(area, buffer, state);
// Handle input
if (event.code() == KeyCode.ENTER || (event.code() == KeyCode.CHAR && event.character() == ' ')) {
state.toggle();
}
State methods:
-
state.isChecked()/state.value()- Get checked state -
state.setChecked(boolean)/state.setValue(boolean)- Set state -
state.toggle()- Toggle and return new value
CSS properties:
-
checkbox-checked-symbol- Symbol when checked (default: "[x]") -
checkbox-unchecked-symbol- Symbol when unchecked (default: "[ ]") -
checkbox-checked-color- Foreground color when checked -
checkbox-unchecked-color- Foreground color when unchecked
Toggle
A toggle switch widget with two display modes:
// Create state
ToggleState state = new ToggleState(); // off
ToggleState stateOn = new ToggleState(true); // on
// Single symbol mode (default): [ON ] or [OFF]
Toggle toggle = Toggle.builder()
.onSymbol("o ON ")
.offSymbol("o OFF")
.onColor(Color.GREEN)
.build();
// Inline choice mode: o Yes / o No
Toggle toggleInline = Toggle.builder()
.inlineChoice(true)
.onLabel("Yes")
.offLabel("No")
.selectedIndicator("o")
.unselectedIndicator("o")
.selectedColor(Color.GREEN)
.unselectedColor(Color.DARK_GRAY)
.build();
toggle.render(area, buffer, state);
// Handle input
if (event.code() == KeyCode.ENTER || (event.code() == KeyCode.CHAR && event.character() == ' ')) {
state.toggle();
}
State methods:
-
state.isOn()/state.value()- Get on/off state -
state.setOn(boolean)/state.setValue(boolean)- Set state -
state.toggle()- Toggle and return new value
CSS properties (single mode):
-
toggle-on-symbol- Symbol when on (default: "[ON ]") -
toggle-off-symbol- Symbol when off (default: "[OFF]") -
toggle-on-color- Foreground color when on -
toggle-off-color- Foreground color when off
CSS properties (inline choice mode):
-
toggle-selected-indicator- Selected option indicator (default: "●") -
toggle-unselected-indicator- Unselected option indicator (default: "○") -
toggle-selected-color- Color for selected option -
toggle-unselected-color- Color for unselected option
Select
An inline select widget showing the current selection with navigation indicators:
// Create state with options
SelectState state = new SelectState("Option A", "Option B", "Option C");
// Build widget: < Option A >
Select select = Select.builder()
.leftIndicator("< ")
.rightIndicator(" >")
.selectedColor(Color.CYAN)
.indicatorColor(Color.DARK_GRAY)
.build();
select.render(area, buffer, state);
// Handle navigation
if (event.code() == KeyCode.LEFT) {
state.selectPrevious(); // wraps around
} else if (event.code() == KeyCode.RIGHT) {
state.selectNext(); // wraps around
}
State methods:
-
state.selectedValue()/state.value()- Get selected option text -
state.selectedIndex()- Get selected index -
state.options()- Get all options -
state.optionCount()- Get number of options -
state.selectIndex(int)- Select by index -
state.selectNext()/state.selectPrevious()- Navigate (wraps) -
state.selectFirst()/state.selectLast()- Jump to ends -
state.setOptions(…)- Replace available options
CSS properties:
-
select-left-indicator- Left navigation indicator (default: "< ") -
select-right-indicator- Right navigation indicator (default: " >") -
select-selected-color- Foreground color for selected value -
select-indicator-color- Foreground color for indicators
Utility Widgets
Scrollbar
Visual indicator of scroll position:
Scrollbar scrollbar = Scrollbar.builder()
.orientation(ScrollbarOrientation.VERTICAL_RIGHT)
.thumbStyle(Style.EMPTY.fg(Color.CYAN))
.trackStyle(Style.EMPTY.fg(Color.DARK_GRAY))
.build();
ScrollbarState state = new ScrollbarState();
state.contentLength(100);
state.viewportContentLength(20);
state.position(scrollPosition);
scrollbar.render(area, buffer, state);
Logo
Renders the Tamboui logo for use in help or about screens:
// Default tiny logo (2 lines)
Logo logo = Logo.tiny();
logo.render(area, buffer);
// Or the normal size (4 lines)
Logo logoNormal = Logo.of(Logo.Size.NORMAL);
logoNormal.render(area, buffer);
Available sizes:
-
Logo.Size.TINY- 2-line compact logo using braille characters -
Logo.Size.NORMAL- 4-line logo using box-drawing characters
ErrorDisplay
Displays exception information with a formatted stack trace:
// Quick creation from exception
ErrorDisplay display = ErrorDisplay.from(exception);
// Or with customization
ErrorDisplay displayCustom = ErrorDisplay.builder()
.error(exception)
.title(" CRASH ")
.footer(" Press 'q' to quit, arrows to scroll ")
.borderColor(Color.RED)
.scroll(scrollOffset)
.build();
display.render(area, buffer);
// Handle scrolling
int totalLines = display.lineCount();
if (event.code() == KeyCode.DOWN) {
scrollOffset = Math.min(scrollOffset + 1, totalLines - visibleHeight);
} else if (event.code() == KeyCode.UP) {
scrollOffset = Math.max(0, scrollOffset - 1);
}
The widget displays:
-
Exception type in red and bold
-
Error message
-
Full stack trace (scrollable)
-
Footer with instructions
Clear
Clears an area by filling it with a style. Useful for widget layering:
// Clear is a singleton widget with no style support
Clear.INSTANCE.render(area, buffer);
Using Widgets with Toolkit DSL
The Toolkit DSL provides fluent factories for all widgets:
// Text
text("Hello").bold().cyan();
// Panel (Block)
panel("Title", text("child")).rounded().borderColor(Color.CYAN);
// List (manages its own state)
list("Item 1", "Item 2", "Item 3").highlightColor(Color.YELLOW).autoScroll();
// Table
table()
.header("Name", "Age")
.row("Alice", "30")
.state(tableState);
// Gauge
gauge(0.75).label("Loading...").gaugeColor(Color.GREEN);
// Sparkline
sparkline(1, 4, 2, 8, 5).color(Color.CYAN);
// Spinner (manages its own state, advances each render)
spinner().cyan();
spinner(SpinnerStyle.LINE, "Loading...").green();
// Tree (expand/collapse, keyboard navigation, lazy loading)
tree(
TreeNode.of("Root",
TreeNode.of("Child 1"),
TreeNode.of("Child 2"))
).highlightColor(Color.CYAN).rounded().scrollbar();
// TextInput
textInput(inputState).placeholder("Enter name...");
// Layout
row(left, right).spacing(1);
column(top, middle, bottom).spacing(1);
For layout DSL examples (columns, grid, dock, stack, flow), see Layouts Reference.
See API Levels for more details on the Toolkit DSL.