Aar's TUI supports switchable color themes and configurable layout sections. Themes control every color in the interface — panel borders, text styles, badges, the input prompt, and the fixed status bars. Layout controls which sections are visible.
# launch with a specific theme
aar tui --theme decker
aar tui --theme sleek
# launch in full-screen mode with fixed header/footer bars
aar tui --fixed
aar tui --fixed --theme decker
# switch themes at runtime (inside the TUI)
/theme # list available themes
/theme decker # switch to a theme by name
/theme next # cycle to the next theme| Name | Description |
|---|---|
default |
Warm amber palette (Bernstein) — the default look. |
contrast |
Classic Aar palette — green, yellow, cyan, red. |
decker |
Neon glow — cyberpunk terminal aesthetic with cyan, magenta, and orange. |
sleek |
Tight spacing, minimal chrome — compact and modern. |
In your config file (~/.aar/config.json), add a tui section:
{
"provider": { "name": "ollama", "model": "llama3" },
"tui": {
"theme": "decker"
}
}The --theme CLI flag overrides the config file, and /theme commands override both at runtime.
Run aar init to set up ~/.aar/themes/ with:
example.json— a full template (copy of the decker theme) ready to rename and edittheme.schema.template— the JSON schema template for editor autocompletion
Create a JSON file at ~/.aar/themes/<name>.json. You only need to include the fields you want to override — everything else falls back to defaults.
{
"name": "nord",
"description": "Nord-inspired arctic palette",
"assistant": {
"title_style": "bold #88c0d0",
"border_style": "#88c0d0"
},
"prompt_style": "bold #88c0d0",
"dim_text": "#4c566a"
}Save this as ~/.aar/themes/nord.json, then use it:
aar tui --theme nordEvery configurable field with its default value:
{
"name": "mytheme",
"description": "A complete custom theme",
"assistant": {
"title_style": "bold green",
"border_style": "green",
"padding": [1, 2]
},
"tool_call": {
"title_style": "bold yellow",
"border_style": "yellow",
"padding": [0, 2]
},
"tool_result": {
"title_style": "bold cyan",
"border_style": "cyan",
"padding": [0, 2]
},
"tool_error": {
"title_style": "bold red",
"border_style": "red",
"padding": [0, 2]
},
"reasoning": {
"title_style": "dim",
"border_style": "dim",
"padding": [0, 2]
},
"error": {
"title_style": "bold red",
"border_style": "red",
"padding": [0, 2]
},
"welcome": {
"title_style": "bold blue",
"border_style": "blue",
"padding": [1, 2]
},
"prompt_style": "bold blue",
"dim_text": "dim",
"working_style": "dim italic",
"path_highlight": "bold blue",
"usage_style": "dim",
"badges": {
"read": "dim cyan",
"write": "yellow",
"execute": "red",
"network": "blue",
"external": "magenta"
},
"header": {
"background": "on #1a1a2e",
"text_style": "bold white",
"separator_style": "dim",
"provider_style": "bold cyan",
"tokens_style": "dim green",
"session_style": "dim",
"state_style": "bold yellow"
},
"footer": {
"background": "on #1a1a2e",
"text_style": "bold white",
"separator_style": "dim",
"step_style": "dim cyan",
"theme_style": "dim magenta",
"input_style": "bold blue"
}
}All style fields accept Rich style strings. You can combine:
- Named colors:
red,green,cyan,magenta,blue,yellow,white - Hex colors:
#d4a574,#00fff7,#ff2d95 - RGB:
rgb(200, 100, 50) - Modifiers:
bold,dim,italic,underline,strike - Combinations:
bold #88c0d0,italic dim,bold underline red
Each panel section (assistant, tool_call, tool_result, tool_error, reasoning, error, welcome) has three fields:
| Field | Type | Description |
|---|---|---|
title_style |
string | Rich style for the panel title text |
border_style |
string | Rich style for the panel border |
padding |
[v, h] |
Vertical and horizontal padding inside the panel |
| Field | What it styles |
|---|---|
prompt_style |
The > input prompt |
dim_text |
Metadata: session IDs, step counters, hints |
working_style |
The "Working..." indicator |
path_highlight |
File paths in verbose mode |
usage_style |
Token usage counts |
Badges appear on tool calls in verbose mode (--verbose). Each badge field is a single Rich color:
| Field | Badge label |
|---|---|
read |
[read] |
write |
[write] |
execute |
[exec] |
network |
[net] |
external |
[ext] |
Layout controls which TUI sections are visible. Configure it in ~/.aar/config.json:
{
"tui": {
"theme": "default",
"layout": {
"reasoning": { "visible": false },
"token_usage": { "visible": false },
"welcome": { "visible": true }
}
}
}| Section | Description | Default |
|---|---|---|
welcome |
Welcome panel shown at startup | visible |
status_bar |
Session ID, step count, state | visible |
reasoning |
Model thinking/reasoning blocks | visible |
assistant |
Assistant message panels | visible |
tool_call |
Tool invocation panels | visible |
tool_result |
Tool output panels | visible |
token_usage |
Per-turn token count line in the conversation body (default visible). In tui --fixed the header always shows cumulative counts regardless of this setting. |
visible |
Each section accepts:
{ "visible": true, "order": 0 }Extensions can register custom panels. Control their visibility via the extensions key:
{
"tui": {
"layout": {
"extensions": {
"metrics": { "visible": true },
"custom_panel": { "visible": false }
}
}
}
}Pass --fixed to launch the TUI with a persistent header and footer bar, scrollable body with scrollbars, mouse support, and a proper input widget:
pip install "aar-agent[tui-fixed]" # install textual dependency
aar tui --fixed
aar tui --fixed --theme decker --verboseRequires the tui-fixed extra (provides Textual).
- Scrollable body with visual scrollbars and configurable scroll speed
- Mouse wheel scrolling (non-blocking — scroll while the LLM is working)
- Page Up / Page Down keyboard scrolling
- Multi-line input — press Enter to add a new line; press Ctrl+S to send
- Command history — press Ctrl+Up / Ctrl+Down to cycle through previous inputs
- Fixed header showing provider/model, token counts (cumulative; updates after each response), session ID, agent state / streaming indicator, thinking status
- Fixed footer showing step count, theme name, and keyboard shortcut hints
- Configurable layout — reorder, resize, or hide regions per theme
| Shortcut | Action |
|---|---|
| Ctrl+S | Send / submit the message |
| Ctrl+X | Cancel the running agent |
| Ctrl+T | Cycle to next theme |
| Ctrl+K | Toggle thinking/reasoning display |
| Ctrl+L | Clear screen and reset counters |
| Ctrl+G | Open / close the log viewer |
| Ctrl+Q | Quit |
| Page Up / Page Down | Scroll the conversation body |
| Ctrl+Up / Ctrl+Down | Navigate input history (in the input box) |
| Enter | New line in the multi-line input |
All commands from the scrollable TUI also work in fixed mode:
| Command | Action |
|---|---|
/quit, /exit, /q |
Quit |
/status |
Show session info |
/tools |
List available tools |
/policy |
Show safety policy |
/theme |
List themes |
/theme <name> |
Switch theme |
/theme next |
Cycle theme |
/think |
Toggle thinking display |
/clear |
Clear screen |
All hotkeys for the fixed TUI are hardcoded in
agent/transports/keybinds.py as a frozen
KeyBinds dataclass. To remap a key, change its key value there — no config
file or aar init step is needed. Ctrl+Q (quit) is a Textual framework
default and is not declared in KeyBinds.
| Field | Default key | Default label | Description |
|---|---|---|---|
send |
ctrl+s |
send |
Send / submit the message |
cancel |
ctrl+x |
cancel |
Cancel the running agent |
cycle_theme |
ctrl+t |
theme |
Cycle to the next theme |
toggle_thinking |
ctrl+k |
think |
Toggle thinking block visibility |
clear_screen |
ctrl+l |
clear |
Clear the chat screen |
toggle_log_viewer |
ctrl+g |
logs |
Open / close the in-app log viewer |
history_prev |
ctrl+up |
hist↑ |
Navigate to the previous history entry |
history_next |
ctrl+down |
hist↓ |
Navigate to the next history entry |
scroll_up |
pageup |
pg↑ |
Scroll the chat body up |
scroll_down |
pagedown |
pg↓ |
Scroll the chat body down |
Note:
cancelandtoggle_thinkingare registered with Textualpriority=True, meaning they always override any widget-internal key handling for those keys.
+------------------------------------------------------------------------+
| Header bar (fixed) |
| ollama / llama3 | tokens: 1234in/567out | abc… | idle | think:on |
+────────────────────────────────────────────────────────────────────────+
| ┃|
| Scrollable conversation body (with scrollbar) ┃|
| (assistant messages, tool calls, results, reasoning, errors) ┃|
| Click a block to select it for copy ┃|
| ┃|
+────────────────────────────────────────────────────────────────────────+
| > type your message... (↑/↓ for history) |
+────────────────────────────────────────────────────────────────────────+
| Footer bar (fixed) |
| step: 5 | theme: default | Ctrl+S send Ctrl+X cancel Ctrl+T theme … |
+------------------------------------------------------------------------+
All /theme, /status, /tools, /policy, /clear, and /quit commands work in fixed mode.
| Field | What it styles |
|---|---|
background |
Header bar background color |
text_style |
General header text |
separator_style |
Horizontal separator line |
provider_style |
Provider and model name |
tokens_style |
Token count display |
session_style |
Session ID |
state_style |
Agent state label — shows idle, running, streaming… (while streaming), completed, etc. |
tokens_warning_style |
Token count display when warning threshold is crossed (default: bold red) |
usage_warning_style |
Per-turn token line in tui mode when warning threshold is crossed (default: bold red) |
| Field | What it styles |
|---|---|
background |
Footer bar background color |
text_style |
General footer text |
separator_style |
Horizontal separator line |
step_style |
Step counter |
theme_style |
Theme name display |
input_style |
Input status text |
The fixed_layout section in a theme controls the full-screen TUI's region order, sizes, colors, and scrollbar appearance. This is the single source of truth for both layout structure and visual styling.
{
"name": "mytheme",
"fixed_layout": {
"regions": [
{ "name": "header", "size": 3 },
{ "name": "body", "size": null },
{ "name": "input", "size": 3 },
{ "name": "footer", "size": 3 }
],
"body_background": "#0e0e0e",
"input_background": "#111118",
"selected_block_style": "on #2a2a3a",
"scrollbar": {
"enabled": true,
"color": "#444444",
"color_hover": "#666666",
"color_active": "#888888",
"background": "#1a1a1a",
"background_hover": "#222222",
"background_active": "#222222",
"size": 2,
"scroll_speed": 3
}
}
}Each region has:
| Field | Type | Description |
|---|---|---|
name |
string | Region name: header, body, input, footer |
size |
int or null | Fixed height in lines. null = flexible (fills remaining space) |
visible |
bool | Whether to show this region (default: true) |
You can reorder regions by changing the array order. For example, to put the footer above the input:
{
"regions": [
{ "name": "header", "size": 3 },
{ "name": "body" },
{ "name": "footer", "size": 3 },
{ "name": "input", "size": 3 }
]
}Or hide the header entirely:
{
"regions": [
{ "name": "header", "size": 3, "visible": false },
{ "name": "body" },
{ "name": "input", "size": 3 },
{ "name": "footer", "size": 3 }
]
}| Field | Type | Description |
|---|---|---|
body_background |
string | Background color for the scrollable body region |
input_background |
string | Background color for the input widget |
selected_block_style |
string | Rich style applied to the selected block highlight (default: on #2a2a3a) |
| Field | Type | Description |
|---|---|---|
enabled |
bool | Show scrollbar on the body region |
color |
string | Scrollbar thumb color |
color_hover |
string | Thumb color on hover |
color_active |
string | Thumb color while dragging |
background |
string | Scrollbar track color |
background_hover |
string | Track color on hover |
background_active |
string | Track color while dragging |
size |
int | Scrollbar width in characters |
scroll_speed |
int | Lines per scroll tick — mouse wheel and PgUp/PgDn (default: 3) |
Header/footer styles and fixed_layout are all optional — if omitted, the defaults are used.
When Aar looks up a theme name, it checks in order:
- Built-in themes (
default,contrast,decker,sleek) - User themes at
~/.aar/themes/<name>.json - Direct file path (absolute or relative)
Theme files are shared between the scrollable TUI (aar tui) and the full-screen fixed TUI (aar tui --fixed), but each mode uses different subsets of the theme:
| Theme section | aar tui (scrollable) |
aar tui --fixed (full-screen) |
|---|---|---|
Panel styles (assistant, tool_call, etc.) |
yes | yes |
Text styles (prompt_style, dim_text, etc.) |
yes | yes |
| Badge colors | yes | yes |
header / footer bar styles |
no | yes |
fixed_layout (regions, scrollbar, backgrounds) |
no | yes |
A theme designed for aar tui will work in --fixed mode — the header, footer, and fixed_layout fields simply fall back to defaults. Conversely, a theme with fixed_layout works in scrollable mode — those fields are silently ignored.
To create a theme that looks great in both modes, include all sections. See data/themes/example_full.json for a complete template.
- Theme switching is instant — it only affects future output. Already-printed text keeps its original colors.
- You can load a theme from any path:
aar tui --theme ./my-themes/experiment.json - Partial themes are valid. Override just the fields you care about; the rest use defaults.
- Use hex colors for precise control. Named colors depend on your terminal's palette; hex colors don't.
- Run
aar initto get a full example theme and JSON schema in~/.aar/themes/.