---
name: terminal-typing-ui
description: Use when you want "Developer-focused, hacker-like, techy" - Simulates command-line text being typed or executed.
---

# Terminal Typing UI

> **Category:** Premium / Futuristic  -  **Personality:** Developer-focused, hacker-like, techy
>
> **Best use:** Developer tools, cybersecurity
>
> **Ratings:** Professional 4/5 - Casual 3/5 - Exotic 3/5 - Visual impact 3/5 - Difficulty 2/5 - Performance cost 1/5

## What it is

A faux developer terminal that *performs* a CLI session: each command is **typed character-by-character** behind a blinking block caret, then "executes" and streams realistic multi-line **stdout** — braille spinners that resolve to `✓`, unicode `████░░░░` progress bars, an ASCII file tree, a sized-output table and a highlighted success line — before moving on to the next command and looping. Wrapped in macOS-style window chrome (traffic lights, tabs, a live status bar), it instantly signals "this is a real dev tool". Unlike a plain Typewriter effect (which only types one string), this models a whole shell: prompt → input → simulated output. Reach for it on developer-tool, CLI, DevOps or cybersecurity landing pages.

## Dependencies / CDN

**None — pure vanilla JS + CSS.** No libraries. The demo only loads two monospace display fonts (optional — any mono works):

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600;700&family=Martian+Mono:wght@500;600;700&display=swap" rel="stylesheet">
```

## HTML

A static window shell — the body is left empty and filled by JS:

```html
<div class="tt-win" role="img" aria-label="Developer terminal running build, deploy and test commands.">
  <div class="tt-bar2">
    <div class="tt-lights" aria-hidden="true">
      <span class="tt-light tt-r"></span><span class="tt-light tt-y"></span><span class="tt-light tt-gr"></span>
    </div>
    <div class="tt-tabs" aria-hidden="true">
      <span class="tt-tab tt-tab-on">quasar</span><span class="tt-tab">api-gateway</span><span class="tt-tab">+</span>
    </div>
    <span class="tt-winmeta" aria-hidden="true">zsh · 96×28</span>
  </div>

  <div class="tt-body" id="ttBody" aria-hidden="true"></div>   <!-- lines injected here -->

  <div class="tt-status" aria-hidden="true">
    <span class="tt-seg tt-sb-branch">main</span><span class="tt-seg">UTF-8</span>
    <span class="tt-spacer"></span>
    <span class="tt-state tt-state-run" id="ttState">running</span>
    <span class="tt-seg" id="ttClock">--:--</span>
  </div>
</div>
```

## CSS

```css
/* Window + scrolling output body */
.tt-win{max-width:868px;border-radius:14px;overflow:hidden;background:#0c0e16;
  border:1px solid #1c2030;box-shadow:0 44px 110px -34px rgba(0,0,0,.85);
  display:flex;flex-direction:column;font-family:'Fira Code',ui-monospace,monospace}
.tt-body{height:432px;overflow-y:auto;overflow-x:hidden;padding:18px;
  font-size:13.5px;line-height:1.62;color:#cdd6e6;background:#0a0c13}
.tt-ln{white-space:pre-wrap;word-break:break-word}   /* keeps aligned spaces, still wraps */

/* Prompt tokens + the blinking block caret (the signature detail) */
.tt-path{color:#56d4dd}  .tt-branch{color:#c792ea}  .tt-arrow{color:#56d364;font-weight:700}
.tt-cmd{color:#eef2fb}
.tt-caret{display:inline-block;width:8px;height:1.04em;vertical-align:-2px;margin-left:1px;
  background:#56d364;border-radius:1px;box-shadow:0 0 8px rgba(86,211,100,.7);
  animation:tt-blink 1.05s steps(1,end) infinite}
.tt-caret.tt-solid{animation:none}            /* solid while actively typing */
@keyframes tt-blink{0%,50%{opacity:1}50.01%,100%{opacity:0}}

/* Simulated stdout: logs, bars, success */
.tt-dim{color:#5f6b80}  .tt-ok,.tt-mk{color:#56d364}  .tt-info{color:#58a6ff}
.tt-fill{color:#56d364}  .tt-empty{color:#262c3b}  .tt-pct{color:#5eead4}
.tt-success{color:#56d364;font-weight:600;border-left:2px solid #3fb950;
  background:linear-gradient(90deg,rgba(63,185,80,.12),transparent 72%);
  padding:3px 0 3px 9px;margin-left:-11px;border-radius:0 6px 6px 0}

/* Reduced motion: kill the blink (JS also stops typing/looping) */
@media (prefers-reduced-motion:reduce){ .tt-caret{animation:none;opacity:1} }
```

## JavaScript

The whole engine is `setTimeout`-driven `async/await`. Note the single `RM` flag: when reduced-motion is on, `sleep()` resolves instantly and every animated routine short-circuits to its finished state, so the same code renders one completed session statically.

```js
var RM = !!(window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches);
var body = document.getElementById('ttBody');
var SPIN = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏';
var PROMPT = '<span class="tt-path">~/apps/quasar-web</span> '
           + '<span class="tt-branch">(main)</span> <span class="tt-arrow">❯</span> ';
var OK = '<span class="tt-mk">✓</span> ';

function sleep(ms){ return RM ? Promise.resolve() : new Promise(function(r){ setTimeout(r, ms); }); }
function rep(c,n){ var s=''; while(n-->0) s+=c; return s; }
function add(cls, html){ var d=document.createElement('div'); d.className=cls;
  d.innerHTML=html; body.appendChild(d); body.scrollTop=body.scrollHeight; return d; }

// 1) Type a command char-by-char, then "submit" (drop the caret)
async function typeCmd(text){
  var line = add('tt-ln', PROMPT+'<span class="tt-cmd"></span><span class="tt-caret tt-solid"></span>');
  var cmd = line.querySelector('.tt-cmd'), caret = line.querySelector('.tt-caret');
  if(RM){ cmd.textContent = text; caret.remove(); return; }
  for(var i=0;i<text.length;i++){
    cmd.appendChild(document.createTextNode(text[i])); body.scrollTop=body.scrollHeight;
    await sleep(text[i]===' ' ? 60+Math.random()*60 : 34+Math.random()*48);
  }
  caret.classList.remove('tt-solid'); await sleep(360); caret.remove();
}

// 2) A braille spinner that resolves to a green check
async function doSpin(e){
  if(RM){ add('tt-ln tt-ok', OK+(e.done||e.text)); return; }
  var line = add('tt-ln', '<span class="tt-sp">'+SPIN[0]+'</span> '+e.text);
  var g = line.querySelector('.tt-sp'), t0=performance.now(), i=0;
  while(performance.now()-t0 < (e.dur||900)){ g.textContent = SPIN[i++ % SPIN.length]; await sleep(70); }
  line.className='tt-ln tt-ok'; line.innerHTML = OK+(e.done||e.text);
}

// 3) A unicode block progress bar filling over `dur` ms
async function doBar(e){
  var N=22, line = add('tt-ln', '<span class="tt-dim">'+e.text+'</span>  '
    + '<span class="tt-track"></span><span class="tt-pct"></span>');
  var track=line.querySelector('.tt-track'), pct=line.querySelector('.tt-pct');
  function draw(p){ var f=Math.round(p/100*N);
    track.innerHTML='<span class="tt-fill">'+rep('█',f)+'</span><span class="tt-empty">'+rep('░',N-f)+'</span>';
    pct.textContent=' '+Math.round(p)+'%'; }
  if(RM){ draw(100); return; }
  var t0=performance.now(), p=0;
  while(p<100){ p=Math.min(100,(performance.now()-t0)/(e.dur||1200)*100); draw(p); await sleep(48); }
  draw(100);
}

// 4) Drive the session, looping forever (one pass under reduced motion)
var SESSION = [
  { cmd:'quasar build --prod', out:[
    { type:'dim',  text:'· loaded quasar.config.ts · target = edge' },
    { type:'spin', text:'Resolving dependency graph', done:'248 modules linked', dur:850 },
    { type:'bar',  text:'compiling', dur:1300 },
    { type:'success', text:OK+'Build ready in 4.21s' }
  ]}
  /* …deploy + test commands follow the same shape… */
];
async function printEntry(e){
  if(e.type==='spin') return doSpin(e);
  if(e.type==='bar')  return doBar(e);
  add('tt-ln '+ (e.type==='success'?'tt-success':e.type==='dim'?'tt-dim':'tt-info'), e.text);
  await sleep(220);
}
async function run(){
  do {
    body.innerHTML='';
    for(var i=0;i<SESSION.length;i++){
      await typeCmd(SESSION[i].cmd); await sleep(220);
      for(var j=0;j<SESSION[i].out.length;j++) await printEntry(SESSION[i].out[j]);
      await sleep(560);
    }
    add('tt-ln', PROMPT+'<span class="tt-caret"></span>');   // idle prompt
    if(RM) break;
    await sleep(3200);
  } while(true);
}
run();
```

## How it works

- **Typing** appends one text node per character with a small randomised delay (slightly longer on spaces) so the cadence feels human instead of metronomic. A `.tt-caret` block sits after the text and is set `.tt-solid` (no blink) while typing, then blinks once typing pauses, then is removed on "submit".
- **Output is data-driven.** Each command owns an `out` array of `{type, …}` entries; a `printEntry` dispatcher renders each kind (`dim`, `info`, `ok`, `spin`, `bar`, `success`, …) and `await`s a beat so lines stream in rather than dumping at once.
- **Spinner & bar** are time-based loops (`performance.now()` vs a `dur`) that mutate a single element's text — the spinner cycles braille frames `⠋⠙⠹…`, the bar redraws `█`×filled + `░`×empty with a percentage. Both are async, so the session naturally waits for them.
- **Auto-scroll** (`scrollTop = scrollHeight`) after every append keeps the newest line in view, exactly like a real terminal.
- **The whole thing loops** — `run()` clears the body and replays — and the window chrome (traffic lights, tab, status bar with a live clock + running/idle dot) frames it as a genuine app.

## Customization

- **Script your own session:** edit the `SESSION` array — change `cmd` strings and the `out` entries. Add commands freely; the loop adapts.
- **Speed / feel:** tune the typing delay range in `typeCmd`, the spinner `dur`, the bar `dur`, and the inter-line `sleep()` beats.
- **Prompt style:** rewrite `PROMPT` — swap the path, use a `git:(branch)` segment, or a powerline/`❯` glyph. Colour tokens via `.tt-path` / `.tt-branch` / `.tt-arrow`.
- **Palette:** the demo themes around teal `#5eead4` + green `#56d364` + violet `#c792ea` on near-black; recolour the CSS custom properties on `.tt-stage` for a different vibe (amber "hacker", cyan "cyberpunk", etc.).
- **Output vocabulary:** add new entry `type`s (e.g. a `warn`, a JSON block, a table) by extending the `printEntry` switch — the data-driven design makes this a one-line addition.

## Accessibility & performance

- **Respects `prefers-reduced-motion`.** A single `RM` flag makes `sleep()` a no-op and forces `typeCmd`/`doSpin`/`doBar` to their final state, so reduced-motion users see a fully-rendered, static completed session (no typing, no looping, no blink) instead of motion.
- **Screen readers:** the streaming `#ttBody` is `aria-hidden="true"` (it would otherwise spam every injected character); the window carries one descriptive `role="img"` + `aria-label` summarising what's shown.
- **Cheap:** no canvas, no WebGL, no library — just text-node writes and `setTimeout`. Cost is negligible. Still, pause the loop when offscreen if you place several on one page (`IntersectionObserver` → stop calling `run`).
- Keep real text contrast in mind: the dim grey (`#5f6b80`) is for de-emphasised log noise, never primary information.

## Gotchas

- **`white-space: pre-wrap`** on lines is deliberate — it preserves the multiple spaces used to align columns/ASCII art while still wrapping long lines. Plain `nowrap`/`pre` would force horizontal scrolling on mobile.
- **Reduced motion + time-based loops:** because `sleep()` resolves instantly under `RM`, a `while(performance.now()-t0 < dur)` loop would busy-spin and freeze the tab. Every animated routine therefore short-circuits with `if(RM){ …final state…; return; }` *before* the loop. Don't remove those guards.
- **Always auto-scroll after appending,** not before — you need the new node's height included in `scrollHeight`.
- **Glyphs must exist in your font.** Braille spinner (`U+2800` block), block shading (`░▒▓█`), box-drawing (`├ └ ─`) and `❯`/`✓` render in normal Unicode monospace fonts (Fira Code, JetBrains Mono…). Avoid Nerd-Font-only Powerline glyphs (`` branch icon) unless you actually ship that font — they show as tofu boxes.
- **Inject command text as a text node** (`createTextNode`), not `innerHTML`, so a command containing `<`, `&`, etc. can never break the markup or inject HTML.
- **`isolation:isolate` / `overflow:hidden`** on the rounded stage keeps the ambient glow and grid clipped inside the panel — without it the blurred background can bleed past the corners.
</content>
</invoke>
