harveybutler.dev
All posts
2 min read

The little terminal that types itself

How the animated terminal on my home page works — a tiny state machine.

If you've seen the home page, you've met the terminal that types commands on its own. It's one of my favourite kinds of code: small, self-contained, and a bit delightful.

The idea

It's just a loop. For each command, we:

  1. Type it out one character at a time.
  2. Print its "output".
  3. Pause, then move to the next command.

The core loop

The whole thing is a recursive async loop guarded by an alive flag so it tears down cleanly:

useEffect(() => {
  let alive = true;
  const wait = (ms: number) => new Promise((r) => setTimeout(r, ms));
 
  const run = async () => {
    let i = 0;
    while (alive) {
      const c = commands[i % commands.length];
      for (let k = 1; k <= c.cmd.length && alive; k++) {
        setTyping(c.cmd.slice(0, k));
        await wait(38 + Math.random() * 55); // jitter = human feel
      }
      // ...print output, pause, i++
    }
  };
 
  run();
  return () => { alive = false; };
}, [commands]);

The details that sell it

  • Jittered timing so it doesn't feel mechanical.
  • A blinking block cursor made of a single <span> and one keyframe.
  • It honours prefers-reduced-motion by rendering a static frame instead.

Small touches, but together they make a rectangle of text feel alive.