Shayan's Blog

Building an Interactive Terminal Portfolio

Posted on January 2, 2026 | Tags: webdev terminal typescript portfolio

In this post, I’ll walk through how I created the interactive terminal experience that powers this portfolio website.

Why a Terminal?

The idea came from wanting something different from the typical portfolio websites. As a developer who spends most of my day in the terminal, it felt natural to create an interface that feels comfortable and unique.

Tech Stack

The terminal is built with:

  • xterm.js - A professional terminal emulator library
  • TypeScript - For type safety and better development experience
  • Vite - Fast build tool with hot reload
  • pnpm workspaces - For managing the monorepo

Core Architecture

Command System

The heart of the terminal is the command system. Each command is a separate module that implements a simple interface:

1
2
3
4
5
export interface Command {
  name: string;
  description: string;
  execute(args: string[], terminal: Terminal): void;
}

This makes it easy to add new commands without modifying existing code.

Terminal Controller

The TerminalController class handles:

  • User input and key events
  • Command parsing and execution
  • History navigation
  • Tab completion

Here’s a simplified version of how commands are executed:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private executeCommand(input: string) {
  const [commandName, ...args] = input.split(/\s+/);
  const command = this.commandRegistry.getCommand(commandName);

  if (command) {
    command.execute(args, this.terminal);
  } else {
    this.terminal.writeln(`command not found: ${commandName}`);
  }
}

Theme System

The terminal supports both dark and light Catppuccin themes. Themes are defined as color objects and switched dynamically:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function getCatppuccinMochaTheme() {
  const isDark = localStorage.getItem('theme') !== 'light';
  
  return isDark ? {
    background: '#1e1e2e',
    foreground: '#cdd6f4',
    // ... more colors
  } : {
    background: '#eff1f5',
    foreground: '#4c4f69',
    // ... more colors
  };
}

Challenges Faced

1. Terminal Sizing

Making the terminal responsive was tricky. The solution was using the FitAddon from xterm.js and listening to resize events:

1
2
3
4
5
6
const fitAddon = new FitAddon();
terminal.loadAddon(fitAddon);

window.addEventListener('resize', () => {
  fitAddon.fit();
});

2. Command Autocompletion

Implementing tab completion required filtering command names and handling the UI updates:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
private handleTab() {
  const matches = this.commandRegistry.getCommandNames()
    .filter(cmd => cmd.startsWith(this.currentLine.trim()));
  
  if (matches.length === 1) {
    // Auto-complete
    this.currentLine = matches[0];
    this.redrawLine();
  } else if (matches.length > 1) {
    // Show options
    this.terminal.writeln(matches.join('  '));
  }
}

3. History Navigation

Up/down arrow keys navigate through command history. This requires maintaining a history array and managing the current index:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private handleUpArrow() {
  if (this.historyIndex === -1) {
    this.historyIndex = this.history.length - 1;
  } else if (this.historyIndex > 0) {
    this.historyIndex--;
  }
  
  this.currentLine = this.history[this.historyIndex];
  this.redrawLine();
}

Adding New Commands

Adding a new command is straightforward. Here’s the links command:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
export const linksCommand: Command = {
  name: 'links',
  description: 'Show social and professional links',
  execute(_args: string[], terminal: Terminal) {
    const links = [
      { name: 'GitHub', url: 'https://github.com/shayan' },
      { name: 'LinkedIn', url: 'https://linkedin.com/in/shayan' },
      // ... more links
    ];
    
    links.forEach(link => {
      terminal.writeln(`  ${link.name}: ${link.url}`);
    });
  }
};

Future Improvements

Some ideas for the future:

  • Add file system navigation (ls, cd, cat)
  • Implement pipes and command chaining
  • Add syntax highlighting for code output
  • Create a package manager for commands
  • Add animations and sound effects

Conclusion

Building this terminal portfolio was a fun challenge that combined my love for terminal interfaces with modern web development. The modular architecture makes it easy to extend, and TypeScript ensures everything stays maintainable.

If you’re interested in the code, check out the repository linked in the projects section!


Have questions or suggestions? Feel free to reach out!