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

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

use DrevOps\Tui\Builder\Form;
use DrevOps\Tui\Tui;

$tui = (new Tui(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.
dosRetro MS-DOS - the bright white/cyan/yellow CGA palette in a double-line window, painted on its own blue screen.

The colourful themes use 256-colour palettes, mono the greyscale ramp and dos the classic 16-colour CGA set; 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

dos - the CGA blue screen, painted regardless of the terminal background

The dos theme on the CGA blue screen

Dark and light

Dark and light are not separate themes but a mode display option that every theme honours. When no theme is set (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.

(new Tui($form))->theme('frost', ['mode' => 'light']); // force light
(new Tui($form))->theme('frost'); // auto-detect

Writing a theme

A custom theme subclasses DefaultTheme and declares its palette by overriding the appearance atoms it wants to change - title(), value(), marker(), border() and so on. Each atom returns its text wrapped in an ANSI style code with paint(); every role it does not mention keeps the default, including the dark/light mode.

use DrevOps\Tui\Theme\DefaultTheme;
use DrevOps\Tui\Theme\Sgr;

class AquaTheme extends DefaultTheme {
public function title(string $text): string {
return $this->paint($this->isDark ? Sgr::of(Sgr::Bold, Sgr::Cyan) : Sgr::of(Sgr::Bold, Sgr::Blue), $text);
}

public function value(string $text, bool $selected = FALSE): string {
return $this->paint($this->emphasize($this->isDark ? Sgr::of(Sgr::Sky) : Sgr::of(Sgr::Cobalt), $selected), $text);
}
}

Colours come from the Sgr palette map - named cases like Sgr::Cyan or Sgr::Sand composed with Sgr::of(...), so a palette reads as colours rather than raw ANSI numbers.

Override as many atoms as the palette needs - the built-in themes each redeclare the accent-coloured atoms (title(), highlight(), marker(), radio(), caret()) plus value(), indicator(), highlightMatch() and border(). To change how an element is laid out rather than coloured, override a render*() method instead.

Lowest friction: the facade names the class directly, with no registration:

$tui = (new \DrevOps\Tui\Tui($form))->theme(AquaTheme::class);

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