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');
| Name | Palette |
|---|---|
default | Cyan accents on a neutral base - the out-of-the-box look. |
midnight | Violet accents, green values, pink highlights. |
frost | Arctic frost-blue accents, sage values, sand highlights. |
ember | Burnt-orange accents, olive values, gold highlights. |
mono | Hue-free - bold weight, grey levels and reverse video, for maximum compatibility. |
dos | Retro 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
| Dark | Light | |
| Borderless | ||
| Bordered |
frost
| Dark | Light | |
| Borderless | ||
| Bordered |
ember
| Dark | Light | |
| Borderless | ||
| Bordered |
mono
| Dark | Light | |
| Borderless | ||
| 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:
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: