Skip to main content
ansicode
PHP

ANSI escape codes in PHP

PHP CLI mode (`php -r`, `php script.php`) is byte-clean on STDOUT — `echo "\e[31mred\e[0m"` works since PHP 5.4 (the version that introduced `\e` in double-quoted strings; older code uses `"\x1b"` or `chr(27)`). On Windows, Conhost since Windows 10 1709 parses VT natively, but PHP recommends explicitly flipping the mode with `sapi_windows_vt100_support(STDOUT, true)` (available since PHP 7.2) so behaviour is uniform across hosts and CGI fallbacks. For more than ad-hoc echoes reach for **Symfony Console** — the canonical PHP CLI framework that powers Composer, Laravel `artisan`, Symfony `console`, and Drupal `drush`. Its `OutputFormatter` uses XML-like tags (`<info>...</info>`, `<fg=red;bg=white;options=bold>...</>`) that compile to ANSI under the hood and auto-strip when the stream is non-TTY. **Termwind** ships a Tailwind-CSS-for-the-terminal layer (class names like `text-red-500`, `bg-blue-500`, `font-bold`) and underpins PHPStan, Pest, Laravel Zero. **laravel/prompts** offers modern interactive prompts that work standalone outside Laravel.

Recommended libraries

  • symfony/console

    The canonical PHP CLI framework — argv parsing, formatted output, progress bars, table rendering, interactive `QuestionHelper` prompts. Output uses XML-like tags: `<info>green</info>`, `<error>red bg</error>`, `<fg=blue;bg=white;options=bold>...</>`. The formatter auto-strips ANSI when stdout is non-TTY. Used by Composer, Laravel `artisan`, Symfony `console`, Drupal `drush`.

  • nunomaduro/termwind

    Tailwind-CSS-for-the-terminal — render output with class names (`text-red-500`, `bg-blue-500`, `font-bold`, `mt-2`, `px-2`, `rounded`) and HTML-like markup (`render('<div class="text-red-500">oops</div>')`). Built atop Symfony Console's `OutputFormatter`. Underpins PHPStan, Pest, Laravel Zero, Lambdish.

  • laravel/prompts

    Modern interactive prompts — `text`, `password`, `confirm`, `select`, `multiselect`, `search`, `suggest`, with a polished visual design and keyboard navigation. The Laravel 10+ successor to Symfony Console's `QuestionHelper`, but installable and usable from any PHP project.

  • league/climate

    PHP League's CLI toolkit — coloured output, tables, progress bars, animations, padding, draw helpers — with a fluent API (`$climate->red()->bold()->out('oops')`). A lighter alternative to Symfony Console for scripts that don't need full command-routing scaffolding.

Idiomatic patterns

Direct echo — \e is PHP's ESC literal (5.4+)
<?php
echo "\e[1;31merror:\e[0m permission denied\n";

// PHP 5.3 fallback (no \e literal) — use \x1b or chr(27):
echo "\x1b[1;31merror:\x1b[0m permission denied\n";
Symfony Console — tag-formatted output
<?php
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;

$out = new ConsoleOutput();

// <info>, <comment>, <question>, <error> ship by default. Define your own:
$out->getFormatter()->setStyle(
    'fire',
    new OutputFormatterStyle('red', null, ['bold'])
);

$out->writeln('<info>ok:</info> all 142 tests passed');
$out->writeln('<fire>error:</fire> permission denied');
$out->writeln('<fg=blue;bg=white;options=bold>highlighted</> back to normal');
// Auto-strips ANSI when stdout is piped (`php script.php | cat`).
Capability gate — sapi_windows_vt100_support + isatty + NO_COLOR
<?php
// Stack the canonical signals in priority order:
//   --no-color flag → NO_COLOR env → FORCE_COLOR → !isatty → Win VT opt-in
function shouldUseColor(): bool {
    if (in_array('--no-color', $GLOBALS['argv'] ?? [], true)) return false;
    if (getenv('NO_COLOR') !== false) return false;
    if (getenv('FORCE_COLOR') !== false && getenv('FORCE_COLOR') !== '0') return true;

    // Windows: PHP 7.2+ exposes a switch for Conhost VT mode. Returns false
    // on legacy hosts (Windows 7/8) so the caller correctly falls back.
    if (DIRECTORY_SEPARATOR === '\\') {
        return function_exists('sapi_windows_vt100_support')
            && sapi_windows_vt100_support(STDOUT, true);
    }

    // POSIX: STDOUT must be a real TTY.
    return function_exists('posix_isatty') && posix_isatty(STDOUT);
}

$useColor = shouldUseColor();
$paint = fn(string $sgr, string $text): string =>
    $useColor ? "\e[{$sgr}m{$text}\e[0m" : $text;

echo $paint('1;31', 'error:'), ' permission denied', "\n";
Progress bar with Symfony Console ProgressBar
<?php
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutput;

$out = new ConsoleOutput();
$progress = new ProgressBar($out, 100);
$progress->setFormat(' %current%/%max% [%bar%] %percent:3s%%  %elapsed:6s%');
$progress->setBarCharacter('<fg=green>=</>');
$progress->setProgressCharacter('<fg=green>></>');
$progress->setEmptyBarCharacter('<fg=gray>-</>');
$progress->start();

for ($i = 0; $i < 100; $i++) {
    usleep(20_000);
    $progress->advance();
}
$progress->finish();
$out->writeln('');

Related sequences

Other languages