Skip to content

Themes

Luna's TUI uses a theme system for consistent visual styling across the chat interface, tool output, markdown rendering, and status bars. Themes are defined as Lua tables with color and attribute specifications.

Built-in Themes

Luna ships with 4 built-in themes:

ThemeDescription
defaultClassic dark theme with colored roles
onedarkSubdued palette inspired by One Dark
high-contrastLight background for accessibility
solarized-darkSolarized color palette on dark background

Selecting a Theme

In ~/.luna/config.json:

json
{
  "theme": "onedark"
}

Or at startup:

bash
luajit main.lua --theme solarized-dark

Theme Structure

A theme is a Lua table with the following sections:

lua
{
  name = "my-theme",
  background = "black",
  menubar_title = "cyan onblack bold",
  scrollbar_bg = "black",
  scrollbar = "white onblack",
  roles = {
    user         = "green onblack bold",
    assistant    = "blue onblack bold",
    system       = "yellow onblack bold",
    error        = "red onblack bold",
    tool         = "magenta onblack",
    tool_start   = "cyan onblack bold",
    tool_result  = "white onblack dim",
    dim          = "white onblack dim",
    thinking     = "cyan onblack dim",
    thinking_summary = "darkgrey onblack dim",
    compact      = "yellow onblack dim",
  },
  markdown = {
    heading       = "\27[1m\27[36m",
    bold          = "\27[1m\27[33m",
    italic        = "\27[3m\27[35m",
    inline_code   = "\27[32m",
    code_block    = "\27[90m",
    code_border   = "\27[90m",
    code_bg       = "\27[48;5;236m",
    link          = "\27[4m\27[34m",
    strikethrough = "\27[2m",
    quote_prefix  = "\27[90m",
    hr            = "\27[90m",
    table_header  = "\27[1m\27[36m",
    table_sep     = "\27[90m",
  },
  spinner = { "blue" },
}

Color Specification

Role Colors

Role colors use a descriptive string format:

<foreground> [on<background>] [<attribute>]

Examples:

green onblack bold
cyan onblack dim
white onblack
red onwhite bold

Supported Colors

ColorANSI Code
black30
red31
green32
yellow33
blue34
magenta35
cyan36
white37
darkgrey90

Supported Attributes

AttributeANSI Code
bold1
dim2
italic3
underline4
reverse7

Markdown Colors

Markdown styling uses raw ANSI escape sequences for full control:

lua
heading = "\27[1m\27[36m"       -- bold cyan
bold = "\27[1m\27[33m"          -- bold yellow
link = "\27[4m\27[34m"          -- underline blue

The M.ANSI table provides named constants:

lua
local theme = require("ui.theme")
theme.ANSI.bold        -- "\27[1m"
theme.ANSI.red         -- "\27[31m"
theme.ANSI.bg_darkgrey -- "\27[48;5;236m"

Custom Theme

To create a custom theme, register it before the TUI initializes:

lua
local theme = require("ui.theme")

theme.themes["my-custom"] = {
  name = "my-custom",
  background = "black",
  menubar_title = "yellow onblack bold",
  scrollbar_bg = "black",
  scrollbar = "darkgrey onblack",
  roles = {
    user         = "green onblack bold",
    assistant    = "cyan onblack",
    system       = "yellow onblack bold",
    error        = "red onblack bold",
    tool         = "magenta onblack",
    tool_start   = "cyan onblack bold",
    tool_result  = "darkgrey onblack",
    dim          = "darkgrey onblack",
    thinking     = "blue onblack dim",
    thinking_summary = "darkgrey onblack dim",
    compact      = "yellow onblack dim",
  },
  markdown = {
    heading       = theme.ANSI.bold .. theme.ANSI.green,
    bold          = theme.ANSI.bold .. theme.ANSI.yellow,
    italic        = theme.ANSI.italic .. theme.ANSI.magenta,
    inline_code   = theme.ANSI.cyan,
    code_block    = theme.ANSI.darkgrey,
    code_border   = theme.ANSI.darkgrey,
    code_bg       = theme.ANSI.bg_darkgrey,
    link          = theme.ANSI.underline .. theme.ANSI.cyan,
    strikethrough = theme.ANSI.dim,
    quote_prefix  = theme.ANSI.darkgrey,
    hr            = theme.ANSI.darkgrey,
    table_header  = theme.ANSI.bold .. theme.ANSI.green,
    table_sep     = theme.ANSI.darkgrey,
  },
  spinner = { "green" },
}

theme.set_theme("my-custom")

LTUI Rendering Engine

Luna uses LTUI (Lua TUI) built on ncurses for terminal rendering:

  • 256-color support with automatic quantization via quantize_256()
  • Color pair allocation managed by alloc_pair() starting at pair 100
  • Image pair caching for sixel/kitty image rendering (pairs 200+)
  • Wide character support with proper wcwidth for CJK and emoji
  • Dirty-region rendering using CSI 2026 for efficient screen updates

ANSI Helpers

The theme module provides text formatting utilities:

lua
local theme = require("ui.theme")

theme.color("error", "Something went wrong")
theme.color("heading", "Title")

theme.strip_ansi(text)
theme.visible_width(text)
theme.wrap(text, 80)
theme.pad(text, 40)

Listing Available Themes

lua
local theme = require("ui.theme")
local names = theme.list_themes()
for _, name in ipairs(names) do
  print(name)
end

Released under the MIT License.