Write a terminal session in plain text. x-zsh renders an
animated, Powerlevel10k-style zsh — typing, spinners, progress bars, the works —
fully isolated in its own Shadow DOM. It's a walkthrough player, not an emulator.
Recording a terminal is fiddly and the output is a video you can't copy from. x-zsh is just markup — versionable, themeable, and the commands stay selectable.
Keyword verbs and bare lines. No timelines, no JSON, no recording session.
Its styles can't leak out and your page can't leak in. Drop it anywhere.
Jittered keystrokes, a blink at the prompt before each command, real pacing.
Spinners and five real progress-bar styles: pip, wget, curl, cargo, tqdm.
Optional play/pause, step, replay, and infinite looping with a delay.
Every color is a CSS variable on :host. Light and dark built in.
Pick a CDN tag or install from npm. The script auto-registers the
<x-zsh> element on load — there's nothing to initialize.
<!-- unpkg, latest (minified) --> <script src="https://unpkg.com/x-zsh"></script> <!-- or jsDelivr, pinned to a version --> <script src="https://cdn.jsdelivr.net/npm/x-zsh@0.6/x-zsh.min.js"></script>
npm install x-zsh
// side-effect import registers <x-zsh> import 'x-zsh';
<x-zsh os="ubuntu" plugins="node"> cmd: npm create vite@latest my-app success: ✔ Scaffolding project... cmd: cd my-app && npm install progress[2s]: Installing </x-zsh>
<x-zsh> (not <shell>). Register a different
tag with XZsh.register('my-term') if you like.Each terminal has a fixed height, so it scrolls internally and
never makes the page jump. Scroll one into view to play it.
Python · pip progress · light mode
Docker · spinners · looping with controls
git workflow · error then recovery
All five progress-bar styles
Rich prompt — Rocky Linux · Go · Terraform · Kubernetes · AWS
Pinned versions & a custom plugin
Named themes — theme="dracula", theme="catppuccin", …
Right-side tail — clock + last-command status
Compact — icons only (compact)
The content between the tags is a tiny line-based language. Each line is an input the user types, an output the program prints, or a directive. Indentation is stripped, so you can nest it neatly in your HTML.
A bare line is stdout of the command above it. You only reach for a verb when a
line isn't plain output. Only the reserved words below (lowercase, followed by :)
are treated as verbs — any other word: renders as ordinary output, so no escaping.
| Verb | Meaning |
|---|---|
cmd: | The user types a command (prompt + typing animation). |
root: | A command needing root — rendered as sudo <command> on the normal prompt (won't double-prefix if you already wrote sudo). |
type: | The user types a response to a prompt; appends inline to the previous line (e.g. answering [Y/n]). |
key: | A keypress glyph — key: ^C, key: enter, key: tab. |
| Verb | Meaning |
|---|---|
| (bare line) | Standard output. |
warning: | Yellow. |
error: | Red. |
success: | Green. |
info: | Cyan. |
note: | An author's aside, rendered as a dimmed shell comment (# text). |
| Verb | Meaning |
|---|---|
delay[800]: | Pause; renders nothing. |
spinner[2s]: label => done | Spinner for the duration, then ✓ done (the => done part is optional). |
progress[3s]: label | Progress bar from 0→100% over the duration. |
| Verb | Meaning |
|---|---|
clear: | Clear the screen. |
prompt: dir=… branch=… | Change prompt context mid-session — keys user host dir branch git. |
# … | A source comment; never renders. |
cd app (including &&
chains) updates the directory segment; git init/git clone reveals the
branch segment; git checkout -b dev switches the branch;
source .venv/bin/activate (or conda activate) reveals the
python venv segment.The bracket on timed verbs holds a duration and/or a style, in any order:
progress[3s pip], spinner[1.5s line]. Durations accept
800 (ms), 800ms, 2s, 1.5s.
| Progress styles | Modeled on |
|---|---|
block (default) | tqdm / modern pip — 45%|████▌ | |
pip | pip (rich) — ━━━━━╸━━━ 45% |
wget | wget — 45% [====> ] |
curl | curl — ###### 45.0% |
arrow | cargo / configure — [====> ] 45% |
Spinner styles: dots (default), line, bar,
arc, circle.
All attributes are optional. Set them on the <x-zsh> tag.
| Attribute | Default | What it does |
|---|---|---|
os | ubuntu | Prompt icon + brand color. One of ubuntu, debian, macos, arch, fedora, alpine, mint, manjaro, kali, centos, rhel, rocky, alma, opensuse, raspbian, gentoo, void, nixos, popos, elementary, windows, wsl, freebsd, termux. |
mode | dark | dark or light. |
theme | — | Named palette: tokyonight, dracula, nord, catppuccin, gruvbox, solarized, onedark, rosepine. Register more with XZsh.theme(). |
plugins | — | Comma list of prompt segments. Languages/runtimes: node, python, docker, rust, go, ruby, php, java, kotlin, swift, deno, bun, dotnet, elixir, dart, zig, lua, perl, haskell. Infra/cloud: terraform, k8s, aws, gcp. python shows only after a venv is activated. |
user host dir branch | you localhost ~ main | Initial prompt context. |
title | user@host: dir | Window title-bar text. |
prompt-char | ❯ | The prompt symbol before each command — e.g. $, %, #, ➜, λ. |
compact | off | Icons-only prompt — hides the OS name and plugin versions (keeps dir/branch). |
clock | off | Ticking time in the right prompt (tail). A red ✘ code status also appears there after a command whose output had an error:. |
height | — | Fixed screen height (bare number = px, or any CSS length). Scrolls internally, auto-scrolls to the latest line. |
rows | — | Fixed height in text rows (overrides height). |
speed | 34 | Average ms per typed character. |
gap | 900 | ms the prompt blinks before a command starts typing. |
bar | block | Default progress-bar style for the terminal. |
controls | off | Show the step / play-pause / replay control bar. |
loop | off | Replay forever. |
loop-delay | 1400 | ms to wait between loops. |
It plays when scrolled into view and respects prefers-reduced-motion (renders
instantly, no animation).
It's a single dependency-free file — a custom element, a line parser, and an async render loop. Hack on it in minutes.
git clone https://github.com/i-rocky/x-zsh cd x-zsh # any static server works python3 -m http.server 8000 # open http://localhost:8000
{verb, dur, style, text}.:host stylesheet so nothing leaks.Use a built-in palette with theme="…" (tokyonight, dracula, nord,
catppuccin, gruvbox, solarized, onedark, rosepine), register your own, or override the
CSS custom properties directly — they're all themeable:
// keys: bg fg muted accent accent2 ok warn err info comment dir git XZsh.theme('mytheme', { bg: '#101418', fg: '#e6edf3', accent: '#3fb950' });
x-zsh::part(window){ border-radius: 6px; }
x-zsh{ --bg:#1e1e2e; --fg:#cdd6f4; --accent2:#89b4fa; }
OS/plugin segments keep their brand colors; the theme controls the background, text, prompt accent, output colors, and the dir/git segments.
Built-in logos are real brand icons vendored into the package (no runtime
dependency on a third-party repo) and loaded lazily, tinted to the segment color via CSS
mask — so the script stays ~11 KB and a logo is fetched only when used.
The base resolves next to wherever you load x-zsh; override with
XZsh.iconBase. Logos: Simple
Icons (CC0) + a couple from devicon (MIT).
Pin any plugin's version inline with name@version, and register your own
segments — point at a Simple Icons slug, any SVG url, or fall
back to an emoji/text icon:
<x-zsh plugins="node@22.3.0, go@1.23, k8s@staging"> … </x-zsh> // register before the element scrolls into view (e.g. in <head>) XZsh.plugin('acme', { slug: 'docker', txt: 'v2', bg: '#5b21b6', fg: '#fff' }); XZsh.plugin('beta', { url: 'https://…/logo.svg', bg: '#222', fg: '#fff' }); XZsh.os('myos', { name: 'MyOS', slug: 'archlinux', bg: '#222', fg: '#fff' });
import 'x-zsh'; // also expose it under your own hyphenated name XZsh.register('acme-term');