< a museum of the best decades of all time >
C:\SPUD> cd \projects\retro-dos-terminal
[ RETRO-DOS-TERMINAL.PROJECT ]
A DOS-style terminal emulator for React. Because not everything needs to be Unix.
STACK:<TypeScript><React><NPM>
STATUS:[ACTIVE]
HIGHLIGHTS:
  • Plugin architecture for commands, filesystem, and effects
  • Authentic BIOS boot screen simulation
  • CRT effects: scanlines, glow, flicker, curvature
  • Zero dependencies beyond React
retro-dos-terminal
DOS-style terminal emulator for React. Because not everything needs to be Unix.
Why DOS?

Every terminal component I found was trying to be Linux. Bash prompts. Unix paths. That green on black Matrix aesthetic. Unix runs the servers, powers the cloud, and most developers live in it. Fine.

But I grew up on MS-DOS. Backslashes. Drive letters. DIR instead of ls. That's the computing that shaped me.

I'd already built a command prompt into this site. Not just a gimmick. There are over ten achievements hidden in there, games, puzzles, the whole deal. But the code was messy. Hard to extend. Fun, but fragile. The more I worked on it, the more I wanted to see if I could turn it into something real. A proper package. Clean architecture. Something other people could actually use.

Partly a challenge to myself. Partly because nobody else was making DOS terminals for React. So I did.

What It Does

retro-dos-terminal is a React component that looks and feels like an actual DOS prompt from 1995. Not a Unix terminal with a different font. A proper DOS experience.

Installation
npm install retro-dos-terminal
Basic Usage
import { Terminal } from "retro-dos-terminal";
import "retro-dos-terminal/styles.css";

<Terminal
  welcomeMessage={["Welcome to DOS!", "Type HELP for commands."]}
  onExit={() => console.log("Goodbye!")}
/>;

That's it. You have a working terminal. But the real power is in the plugins.

Plugin Architecture

I wanted this thing to be flexible. Not everyone needs a filesystem. Not everyone wants CRT effects. So instead of bundling everything into one bloated component, I made plugins.

Filesystem
import { filesystem } from 'retro-dos-terminal/plugins/filesystem'
import { dir, cd } from 'retro-dos-terminal/plugins/commands'

const files = {
  'C:\\': [
    { type: 'folder', name: 'GAMES', modified: new Date(1995, 11, 25) },
    { type: 'file', name: 'README.TXT', size: 128, content: ['Hello, World!'] },
  ],
  'C:\\GAMES': [
    { type: 'file', name: 'SNAKE.EXE', executable: true, size: 24576 },
  ],
}

<Terminal plugins={[filesystem({ files }), dir(), cd()]} />

Now you have DIR and CD commands that actually work. Navigate around. List files. It feels real because the structure is real.

CRT Effects
import { crt } from "retro-dos-terminal/plugins/effects";
import "retro-dos-terminal/plugins/effects/styles.css";

<Terminal
  plugins={[
    crt({
      scanlines: true,
      scanlineOpacity: 0.15,
      glow: true,
      flicker: true,
      flickerIntensity: 0.015,
      curvature: true,
      curvatureAmount: 0.2,
    }),
  ]}
/>;

Scanlines. Phosphor glow. That subtle flicker of a CRT monitor. Screen curvature if you want to go full retro. It's all optional. But when you turn it on? Chef's kiss.

Command History (DOSKEY)
import { doskey } from "retro-dos-terminal/plugins/doskey";

<Terminal plugins={[doskey({ maxHistory: 50 })]} />;

If you used DOS, you remember the moment you discovered DOSKEY. Before that, every mistyped command meant retyping the whole thing. Then DOS 5.0 came along and suddenly you could press the up arrow to recall your last command. It felt like magic.

The doskey plugin brings that back. Arrow keys navigate through command history. It's a small thing, but it makes the terminal feel real instead of frustrating.

Custom Programs
import { programs, defineProgram } from 'retro-dos-terminal/plugins/programs'

const myGame = defineProgram({
  commands: ['game', 'game.exe'],
  onStart: (ctx) => {
    ctx.print('Welcome to my game!')
    ctx.print('Type QUIT to exit.')
  },
  onInput: (input, ctx) => {
    if (input.toLowerCase() === 'quit') {
      ctx.get('exitProgram')?.()
      return { handled: true }
    }
    ctx.print(`You typed: ${input}`)
    return { handled: true }
  },
})

<Terminal plugins={[programs([myGame])]} />

Build interactive programs that take over the terminal. Games. Utilities. Whatever you want. The program handles its own input until it exits.

BIOS Boot Screen

Because why not?

import { BIOSBoot } from 'retro-dos-terminal/boot'

<BIOSBoot
  biosName="Award Modular BIOS"
  biosVersion="v4.51PG"
  cpu="Intel Pentium-S CPU at 75MHz"
  memory={640}
  speed="authentic"
  onComplete={() => setBooted(true)}
>
  <Terminal ... />
</BIOSBoot>

A full POST sequence. Memory counting. Drive detection. That satisfying beep. Then it hands off to your terminal. Is it necessary? Absolutely not. Is it fun? Absolutely yes.

Themes

Three built-in themes:

  • DOS gray: The classic IBM gray. Authentic. Boring. Perfect.
  • phosphor-green: That green glow. The one everyone pictures when they think "hacker."
  • phosphor-amber: For those of us who remember amber monitors. Warmer. Easier on the eyes.

Or build your own. Every color, every font setting, every cursor style is configurable.

Why I Built This

I wanted to see if I could make a real npm package. Not just code that works, but code that's structured well enough for other people to use. Exports that make sense. A plugin system that doesn't fight you. Documentation that actually helps.

The terminal on this site was the perfect candidate. Already working. Already fun. But held together with duct tape and good intentions. Turning it into a proper package meant rethinking everything.

Technical Details

Zero Dependencies

  • Just React as a peer dependency
  • No runtime bloat
  • Tree-shakeable, so you only bundle the plugins you use

TypeScript First

  • Full type definitions
  • Autocomplete for everything
  • Catches bugs before they ship

Modern Build

  • ESM and CommonJS
  • Works with Next.js, Vite, whatever
  • Proper exports map for clean imports
What I Learned

Building a plugin system is harder than it looks. You want flexibility without complexity. You want plugins that compose nicely without stepping on each other. Getting the API right took several iterations.

CRT effects are also deceptively tricky. Real CRTs had physics. Phosphor decay. Beam convergence. Simulating that with CSS means approximating a lot. The scanlines are easy. The glow takes work. The flicker has to be subtle enough to feel authentic without giving people headaches.

And fonts matter more than you'd think. The package bundles Perfect DOS VGA 437, which nails the authentic DOS look. But here's the thing: that font isn't fully monospaced with certain symbols, which can break terminal alignment. On this site I swap in IBM Plex Mono for reliability.

Open Source

The code is on GitHub. MIT licensed. Use it for whatever.

Maybe you want a retro aesthetic for your portfolio. Maybe you're building something weird and a DOS prompt fits. Maybe you just think scanlines look cool. Whatever the reason:

npm install retro-dos-terminal

And if you find bugs, let me know. There are always bugs.