Termynal: Why Async/Await Changed Terminal Animation Forever
Hook
Every flashy terminal animation you've seen on developer landing pages probably breaks the user's ability to copy-paste commands—but it doesn't have to.
Context
Documentation has a UX problem. When you're showcasing CLI tools or installation instructions, static code blocks feel lifeless, but animated demos often sacrifice accessibility and usability for visual flair. Traditional approaches either embedded GIFs (inaccessible, huge file sizes, not selectable), used heavy JavaScript libraries that created callback hell with nested setTimeout chains, or implemented full terminal emulators (massive overkill for simple documentation). The fundamental tension was between making documentation engaging and keeping it functional.
Termynal emerged from this gap with a different philosophy: progressive enhancement. Created by Ines Montani (co-founder of Explosion AI, makers of spaCy), it treats animation as a layer on top of already-functional HTML. Without JavaScript, users see complete, selectable command listings. With JavaScript, they get smooth typing animations. This seemingly simple shift changes the entire architecture—instead of JavaScript generating content, it orchestrates the reveal of content that's already in the DOM.
Technical Insight
The architectural brilliance of Termynal lies in how it leverages async/await to transform what would traditionally be callback spaghetti into linear, readable code. When you initialize a Termynal instance, it doesn't immediately start animating. Instead, it builds a promise chain that respects the natural flow of terminal interactions.
Here's a minimal implementation:
<div id="termynal" data-termynal>
<span data-ty="input">npm install awesome-package</span>
<span data-ty="progress"></span>
<span data-ty>Successfully installed!</span>
<span data-ty="input">awesome-package --version</span>
<span data-ty>v2.1.0</span>
</div>
<script src="termynal.js"></script>
<script>new Termynal('#termynal');</script>
Under the hood, Termynal reads each data-ty attribute to determine line type (input, output, or progress indicator), then hides the text content with CSS. The animation loop uses async/await to sequence each line:
async function animate() {
for (let line of this.lines) {
await this.type(line);
await this.wait(line.delay);
}
}
async type(line) {
const chars = line.textContent.split('');
for (let char of chars) {
// Render char
await this.wait(this.typeDelay);
}
}
This pattern eliminates the pyramid of doom you'd get with callbacks or chained .then() statements. Each animation step is a discrete async function that resolves when complete, making the flow readable and debuggable.
The CSS pseudo-element trick is equally clever. Input prompts (the $ or > symbols) aren't part of the actual text content—they're rendered via :before pseudo-elements:
[data-ty="input"]:before {
content: '$';
margin-right: 0.75em;
color: var(--color-text-subtle);
}
This means when users select and copy terminal commands, they get clean text without the prompt symbols. It's a small detail that dramatically improves usability—users can paste commands directly without manual cleanup.
Termynal also supports dynamic line injection for runtime-generated content:
const termynal = new Termynal('#termynal', {
noInit: true
});
// Add lines programmatically
termynal.lines = [
{ type: 'input', value: 'git clone https://github.com/user/repo.git' },
{ type: 'progress' },
{ value: 'Cloning into repository...' },
{ delay: 1000, value: 'Done!' }
];
termynal.init();
The library pre-calculates container dimensions on initialization to avoid forced reflows during animation. By reading offsetWidth and offsetHeight once and caching them, it prevents layout thrashing that would occur if dimensions were checked on every frame. This attention to performance detail keeps animations smooth even on lower-powered devices.
The progressive enhancement philosophy extends to the initialization logic. Termynal checks for data-termynal attributes and auto-initializes matching elements, but won't throw errors if the elements don't exist. This makes it safe to include in universal JavaScript bundles without defensive coding.
Gotcha
The async/await requirement means Internet Explorer 11 is completely out without polyfills—a non-issue for most developer-facing documentation in 2024, but potentially problematic if you're building enterprise internal tools where ancient browsers linger. More importantly, there's no TypeScript definition file in the repository, so you'll need to write your own declarations or work without type safety.
Termynal is deliberately not a terminal emulator. You can't handle user input, execute commands, or create interactive sessions. It's purely presentational. If users expect to type into what looks like a terminal, they'll be confused when nothing happens. This also means you can't replay actual terminal sessions—you're manually authoring each line as HTML or JavaScript objects. For documenting complex CLI interactions with branching paths or error handling, you'll end up with verbose markup that becomes hard to maintain. The library excels at linear command sequences but struggles with representing the messier reality of terminal usage.
Verdict
Use Termynal if you're building developer documentation, tutorial sites, or product landing pages where you need lightweight terminal animations that respect accessibility and user experience. It's perfect when you want visual polish without sacrificing progressive enhancement, when your audience is modern browsers, and when your terminal demonstrations are curated sequences rather than actual recorded sessions. The 4KB payload and zero-dependency footprint make it ideal for performance-conscious projects. Skip it if you need actual terminal emulation, interactive command execution, or support for legacy browsers. Also skip if you're trying to showcase complex terminal interactions with multiple branches—at that point, recorded asciinema sessions or even video might serve users better than manually crafted animations that oversimplify reality.