From pixels to characters: The engineering behind GitHub Copilot CLI’s animated ASCII banner

Learn how GitHub built an accessible, multi-terminal-safe ASCII animation for the Copilot CLI using custom tooling, ANSI color roles, and advanced terminal engineering.

Aaron Winston
17 min readintermediate
--
View Original

Overview

This article details the engineering challenges behind building an animated ASCII banner for GitHub Copilot CLI, revealing how a seemingly simple 3-second animation required over 6,000 lines of TypeScript. It covers terminal rendering constraints, ANSI color inconsistencies, accessibility-first design decisions, and the custom toolchain built to bridge the gap between design and CLI engineering.

What You'll Learn

1

Why animated ASCII in terminals is a fundamentally harder engineering problem than web animation

2

How to design a semantic ANSI color system that degrades gracefully across terminals and accessibility settings

3

How to build frame-based terminal animations using Ink (React for the terminal) with flicker-free rendering

4

How to separate animation content from styling using typed animation elements and theme mappings in TypeScript

5

When and why to make CLI animations opt-in for accessibility compliance

Prerequisites & Requirements

  • Basic understanding of terminal emulators and ANSI escape codes
  • Familiarity with React component patterns (used by Ink for terminal rendering)
  • Understanding of TypeScript type systems and interfaces
  • Node.js and TypeScript development environment
  • Basic awareness of WCAG accessibility guidelines(optional)

Key Questions Answered

Why is building animated ASCII art in a terminal so much harder than web animation?
Terminals treat output as a stream of characters with no native concept of frames, sprites, z-index, or animation tick rates. Every frame must be manually repainted using cursor movements and ANSI control sequences. There's no compositor, no consistent rendering model, and terminals behave differently under load—some throttle fast writes, buffer output differently, or repaint cursor regions inconsistently. Unlike browsers with shared rendering engines, terminal environments are a patchwork of behaviors inherited from decades-old hardware like the VT100.
How do you handle color consistency across different terminal emulators?
Instead of committing specific RGB values, the GitHub Copilot CLI team mapped high-level semantic roles (eyes, goggles, shadow, border) to ANSI color slots that terminals can reinterpret safely. They chose a minimal 4-bit ANSI palette—one of the few color modes most terminals allow users to customize—ensuring the animation remained legible under high-contrast themes, low-vision settings, and color overrides. Separate light and dark themes map animation elements to appropriate ANSI color names.
How does the Copilot CLI animation avoid flickering during terminal rendering?
The team kept the animation under three seconds to avoid delaying user interaction, separated static and non-static components to minimize unnecessary redraws, initialized background services (MCP servers, custom agents) without blocking render, and worked within Ink's asynchronous re-rendering model. The animation was treated as a non-blocking, best-effort enhancement—visible when rendered safely, but never at the expense of startup performance or usability.
What is the architecture for maintaining frame-based ASCII animations in TypeScript?
Each animation frame stores content as plain text separately from color information. Colors are mapped using row,col coordinate positions to named animation elements (like 'border', 'head', 'goggles', 'eyes'). These animation elements are then mapped to ANSI colors via theme objects, with separate dark and light theme definitions. At render time, consecutive characters with the same color are grouped into segments and wrapped in Ink Text components with the appropriate color prop.
How do you make CLI animations accessible for screen reader users?
The Copilot CLI animation is opt-in and gated behind its own flag, not shown by default. When developers run the CLI in --screen-reader mode, the banner is automatically skipped so no decorative characters or motion are sent to assistive technologies. The team also minimized ANSI instructions that can confuse assistive tech, respected global color overrides in both terminal and system preferences, and ensured color-based meaning degrades safely since subtle hues may not be perceivable.
What are the three approaches to handling color in CLI applications?
First, use no color at all for broad compatibility but reduced visual communication. Second, use richer color modes (3-bit, 4-bit, 8-bit, or truecolor) that aren't uniformly supported, creating maintenance challenges across terminals and themes. Third, use a minimal customizable palette (usually 4-bit colors) that most terminals allow users to override—the safest path but one that limits brand palette accuracy and forces designing for widely varying contrast and theme choices.
Can Ink (React for the terminal) handle animations natively?
Ink lets you build terminal interfaces using React components rendered to stdout, but it's not an animation engine. It re-renders on every state change, doesn't manage frame deltas, doesn't synchronize with terminal paint cycles, and doesn't solve flicker or cursor ghosting. For the Copilot CLI banner, all animation logic including frame timing, color segment grouping, and flicker prevention had to be handcrafted on top of Ink's component model.
What custom tools were built for the Copilot CLI ASCII animation workflow?
Designer Cameron Foxly built a custom ASCII animation editor from scratch because no existing tools supported frame-by-frame editing with multi-color ANSI previews, color role exporting, Ink-ready component generation, or contrast and accessibility testing. The tool evolved into an open-source application called ASCII Motion (ascii-motion.app) that reads text files as frames, renders them sequentially with controlled timing, includes a color brush tool for painting ANSI colors character-by-character, and clears the screen without flicker.

Key Statistics & Figures

Animation duration
3 seconds
Total animation runtime for the Copilot CLI banner
Animation frames
~20 frames
Number of frames in the banner animation
Codebase size
~6,000 lines of TypeScript
Total code required for the 3-second animation, mostly dedicated to terminal inconsistencies, accessibility, and rendering logic
Frame area
11x78 characters
Dimensions of each animation frame
Animation elements per frame
~10 elements
Number of distinct stylizable animation elements in any given frame
Frame rate
~13 FPS
75ms interval

Technologies & Tools

Some links below are affiliate links. We may earn a commission if you make a purchase.

Language
Typescript
Primary implementation language for the animation system (~6,000 lines)
Framework
Ink
React renderer for building terminal CLI interfaces using JSX components that render to stdout
Framework
React
Component model used by Ink for structuring terminal UI with state-driven rendering
Protocol
Ansi Escape Codes
Terminal color and cursor control sequences for rendering colored ASCII art
Runtime
Node.js
Runtime environment for the CLI application and animation system
AI Tool
Github Copilot
Used by the designer to scaffold the ASCII animation editor MVP and generate Ink components
IDE
Vs Code
Development environment used to build the ASCII animation editor
Tool
Ascii Motion
Custom open-source ASCII animation editor built during the project (ascii-motion.app)

Key Actionable Insights

1
Use semantic color roles instead of hardcoded color values when building terminal UIs. Map high-level concepts like 'border', 'highlight', and 'text' to ANSI color codes, then create separate theme mappings for light and dark terminals. This approach ensures your CLI visuals degrade gracefully when users override terminal colors for accessibility.
The Copilot CLI team found that ANSI color consistency simply doesn't exist across terminals. Even in 256-color mode, terminals remap colors based on user themes, accessibility settings, and OS-level overrides.
2
Treat CLI animations as non-blocking, best-effort enhancements rather than required UI elements. Gate animations behind opt-in flags and automatically skip them in screen-reader mode. This prevents decorative content from degrading the developer experience for users with assistive technologies.
Rapid re-renders create auditory clutter for screen readers, and color-based meaning may not be perceivable by low-vision or color-blind users. The Copilot CLI animation is hidden by default and only shown when explicitly enabled.
3
When building frame-based terminal animations, separate frame content (plain text) from styling information (color mappings) and timing data. Store colors as coordinate-to-role mappings rather than embedding ANSI codes directly in the frame text. This separation makes animations maintainable and allows theme changes without touching frame data.
The Copilot CLI banner's 20 frames covering an 11x78 area with ~10 animation elements per frame would be unmaintainable if colors were embedded directly. The typed AnimationFrame interface with separate content, colors, and duration fields enabled the team to iterate on themes independently.
4
Prefer 4-bit ANSI color palettes over 8-bit or truecolor for maximum terminal compatibility and user customizability. While this limits brand palette accuracy, 4-bit colors are one of the few color modes most terminals allow users to customize, which is critical for accessibility.
Even modern terminals that support truecolor render colors differently based on themes. The WCAG contrast guidance applies differently to text vs. non-text graphical content in terminals, and the Copilot wordmark had to be treated as non-text content.
5
When rendering colored text in Ink components, group consecutive characters with the same color into segments before rendering. This reduces the number of Text component nodes and ANSI escape code sequences, improving rendering performance and reducing flicker in terminals that struggle with rapid updates.
The final rendering code maps each character to its color, then groups consecutive same-colored characters into segments, wrapping each segment in a single Ink Text component rather than creating one per character.
6
Build custom design tooling when no existing workflow supports your medium. For terminal animations, existing ANSI preview tools don't simulate how different terminals remap colors or handle cursor updates, making accurate design iteration impossible without purpose-built tooling that bridges design and engineering.
Cameron Foxly built a custom ASCII animation editor in about an hour using GitHub Copilot because there were virtually no tools for frame-by-frame ASCII editing with multi-color ANSI previews and Ink-ready component export. This tool later became the open-source ASCII Motion project.

Common Pitfalls

1
Hardcoding specific RGB or 256-color values in CLI applications, assuming they'll render consistently across terminals. Even in 256-color mode, terminals remap colors based on user themes, accessibility settings, high-contrast modes, light/dark backgrounds, and OS-level overrides. What looks perfect in your terminal may be illegible in another.
Use semantic color roles mapped to 4-bit ANSI palette colors that terminals allow users to customize, rather than committing to exact hues.
2
Assuming Ink (or similar terminal React renderers) handles animation concerns automatically. Ink re-renders on every state change but doesn't manage frame deltas, synchronize with terminal paint cycles, or solve flicker and cursor ghosting. Developers often expect it to work like a browser rendering engine, but animation logic must be entirely handcrafted.
Build your own frame timing, delta management, and flicker prevention logic on top of Ink's component model.
3
Enabling CLI animations by default without considering accessibility implications. Rapid re-renders create auditory clutter for screen readers, color-based meaning may be invisible to color-blind users, and clearing sequences can confuse assistive technologies. Making animations automatic forces users with disabilities to actively opt out.
Gate animations behind opt-in flags and automatically disable them in screen-reader mode. Follow the pattern of treating animations as best-effort enhancements.
4
Embedding color information directly into ASCII frame content, making it impossible to maintain or theme. With ~20 frames, ~10 animation elements per frame, and separate light/dark requirements, inline color codes create an unmaintainable mess that cannot be updated without touching every frame.
Separate frame content (plain text) from color mappings (coordinate-to-role) and themes (role-to-ANSI-color), allowing independent iteration on each concern.
5
Setting animation frame rates too high (e.g., 60 FPS) in terminal environments. Unlike browsers with optimized rendering pipelines, terminals can throttle fast writes, reveal cleared frames momentarily, buffer output differently, and repaint cursor regions inconsistently, resulting in visible flicker.
The Copilot CLI team used 75ms intervals (~13 FPS) as a safe frame rate. Higher rates can cause flicker in some terminals.

Related Concepts

Ansi Escape Codes
Terminal Emulator Rendering
Vt100 Terminal Standard
Wcag Accessibility Guidelines
CLI Accessibility
Screen Reader Compatibility
Ink React Terminal Renderer
Frame-based Animation
Color Theory For Terminals
4-bit Vs 8-bit Vs Truecolor Palettes
Non-blocking UI Rendering
Semantic Color Systems
Design-to-engineering Toolchain
Github Copilot SDK
Mcp (model Context Protocol)
Agentic CLI Workflows