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 theme below is shown in its four looks: the dark and light palette, each without a frame and inside the rounded border (['border' => 'rounded']). Every adaptive theme has a runnable script in playground/09-themes/.

midnight

DarkLight
BorderlessThe midnight theme: dark, borderlessThe midnight theme: light, borderless
BorderedThe midnight theme: dark, borderedThe midnight theme: light, bordered

frost

DarkLight
BorderlessThe frost theme: dark, borderlessThe frost theme: light, borderless
BorderedThe frost theme: dark, borderedThe frost theme: light, bordered

ember

DarkLight
BorderlessThe ember theme: dark, borderlessThe ember theme: light, borderless
BorderedThe ember theme: dark, borderedThe ember theme: light, bordered

mono

DarkLight
BorderlessThe mono theme: dark, borderlessThe mono theme: light, borderless
BorderedThe mono theme: dark, borderedThe mono theme: light, bordered

dos

The CGA blue screen, painted regardless of the terminal background. The theme draws its own double-line window, so it has no bordered/borderless split - the window is its frame:

The dos theme in a dark terminalThe dos theme in a light terminal

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