Skip to main content

Themes

A theme owns the entire visual representation - the palette (per-role ANSI style codes), the glyphs (marker, caret, scroll indicators, separators - each a Unicode/ASCII pair) and how every row is composed. DefaultTheme implements all of it with a neutral base; a concrete theme extends it and overrides only what it changes. The ThemeManager turns a theme name into an instance.

Built-in themes

Five themes ship built-in, each selectable by name:

use DrevOps\Tui\Builder\Form;

Form::create('My form')->theme('midnight')/* ... */;
NamePalette
defaultCyan accents on a neutral base - the out-of-the-box look.
midnightViolet accents, green values, pink highlights.
frostArctic frost-blue accents, sage values, sand highlights.
emberBurnt-orange accents, olive values, gold highlights.
monoHue-free - bold weight, grey levels and reverse video, for maximum compatibility.

The curated themes use 256-colour palettes; every one renders across all widgets and degrades to plain text when colour is off. An unknown theme name fails loudly

  • a typo never silently falls back to the default.

Each adapts to the terminal background - here the dark palette (left) and the light palette (right):

midnight

The midnight theme in dark modeThe midnight theme in light mode

frost

The frost theme in dark modeThe frost theme in light mode

ember

The ember theme in dark modeThe ember theme in light mode

mono

The mono theme in dark modeThe mono theme in light mode

Dark and light

Dark and light are not separate themes but a mode display option that every theme honours. When a form sets no theme (or the explicit 'auto' sentinel), the interactive TUI picks the mode from the actual terminal background: it queries the background colour over OSC 11, falls back to the COLORFGBG environment variable, and settles on dark when neither answers.

Form::create('My form')->theme('frost', ['mode' => 'light']); // force light
Form::create('My form')->theme('frost'); // auto-detect

Writing a theme

A custom theme subclasses DefaultTheme and overrides only what it changes - every role it does not mention keeps working, including the dark/light mode. The quickest way to recolour is the palette seam: five per-role style accessors the appearance atoms read.

use DrevOps\Tui\Theme\DefaultTheme;

class AquaTheme extends DefaultTheme {
protected function accentSgr(): string { return $this->isDark ? '1;38;5;44' : '1;38;5;24'; }
protected function valueSgr(): string { return $this->isDark ? '38;5;80' : '38;5;30'; }
}

accentSgr() drives the title, the highlighted row, the marker, the radio dot and the caret; valueSgr(), indicatorSgr(), matchSgr() and borderSgr() colour the remaining roles. For finer control, override an individual appearance atom (title(), marker(), caret()…) or a render*() method to change how an element is laid out.

Lowest friction: a form names the class directly, with no registration:

$form = Form::create('My form')->theme('\App\AquaTheme')/* ... */;

Or register a short alias with ThemeManager::register('aqua', AquaTheme::class), then ->theme('aqua'). The playground's ocean theme goes further, overriding many atoms and render*() methods for a distinct look with a start banner:

Custom ocean theme with a banner