terminals-and-code

Terminals and code blocks need to be readable, beautiful, and functional.

Terminals and Code Display

Terminals and code blocks need to be readable, beautiful, and functional.

Terminal Interfaces

Terminals are where developers feel at home. Make them beautiful with Zen Mono.

Visual Design

.terminal {
  background: #0d1117; /* GitHub dark background */
  color: #c9d1d9;
  font-family: 'Zen Mono', monospace;
  font-size: 14px;
  line-height: 1.5;
  padding: 1rem;
  border-radius: 8px;
  overflow: auto;
}

.terminal-prompt {
  color: #58a6ff; /* Bright blue */
  user-select: none;
}

.terminal-prompt::before {
  content: '$ ';
  color: #8b949e; /* Muted gray */
}

.terminal-output {
  white-space: pre-wrap;
  word-break: break-word;
}

ANSI Color Support

Support standard ANSI escape codes for colored terminal output.

// ANSI color mapping (standard 16 colors)
const ansiColors = {
  // Normal colors (30-37)
  30: '#24292f', // black
  31: '#cf222e', // red
  32: '#1a7f37', // green
  33: '#9a6700', // yellow
  34: '#0969da', // blue
  35: '#8250df', // magenta
  36: '#1b7c83', // cyan
  37: '#6e7781', // white
  
  // Bright colors (90-97)
  90: '#57606a', // bright black (gray)
  91: '#ff6b6b', // bright red
  92: '#4ade80', // bright green
  93: '#fbbf24', // bright yellow
  94: '#60a5fa', // bright blue
  95: '#c084fc', // bright magenta
  96: '#22d3ee', // bright cyan
  97: '#f5f5f5', // bright white
};

function parseAnsi(text: string): React.ReactNode[] {
  const ansiRegex = /\x1b\[([0-9;]+)m/g;
  const parts: React.ReactNode[] = [];
  let lastIndex = 0;
  let currentColor: string | null = null;

  let match;
  while ((match = ansiRegex.exec(text)) !== null) {
    // Add text before this code
    if (match.index > lastIndex) {
      const textPart = text.slice(lastIndex, match.index);
      parts.push(
        currentColor ? (
          <span key={parts.length} style={{ color: currentColor }}>
            {textPart}
          </span>
        ) : (
          textPart
        )
      );
    }

    // Parse color code
    const codes = match[1].split(';').map(Number);
    if (codes[0] === 0) {
      currentColor = null; // reset
    } else if (ansiColors[codes[0]]) {
      currentColor = ansiColors[codes[0]];
    }

    lastIndex = match.index + match[0].length;
  }

  // Add remaining text
  if (lastIndex < text.length) {
    const textPart = text.slice(lastIndex);
    parts.push(
      currentColor ? (
        <span key={parts.length} style={{ color: currentColor }}>
          {textPart}
        </span>
      ) : (
        textPart
      )
    );
  }

  return parts;
}

Terminal Features

Essential features:

function Terminal() {
  const [history, setHistory] = useState<string[]>([]);
  const [historyIndex, setHistoryIndex] = useState(-1);
  const [input, setInput] = useState('');
  const [output, setOutput] = useState<string[]>([]);

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'ArrowUp') {
      e.preventDefault();
      if (historyIndex < history.length - 1) {
        const newIndex = historyIndex + 1;
        setHistoryIndex(newIndex);
        setInput(history[history.length - 1 - newIndex]);
      }
    } else if (e.key === 'ArrowDown') {
      e.preventDefault();
      if (historyIndex > 0) {
        const newIndex = historyIndex - 1;
        setHistoryIndex(newIndex);
        setInput(history[history.length - 1 - newIndex]);
      } else if (historyIndex === 0) {
        setHistoryIndex(-1);
        setInput('');
      }
    } else if (e.key === 'Enter') {
      handleCommand(input);
      setHistory([...history, input]);
      setHistoryIndex(-1);
      setInput('');
    }
  };

  return (
    <div className="terminal">
      <div className="terminal-output">
        {output.map((line, i) => (
          <div key={i}>{parseAnsi(line)}</div>
        ))}
      </div>
      <div className="terminal-input-line">
        <span className="terminal-prompt">$</span>
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={handleKeyDown}
          className="terminal-input"
        />
      </div>
    </div>
  );
}

Terminal Aesthetics

Three style options:

  1. clean-professional (recommended):
.terminal-clean {
  background: #0d1117;
  /* No effects, perfect clarity */
}
  1. retro-modern (subtle effects):
.terminal-retro {
  background: #0d1117;
  text-shadow: 0 0 2px currentColor;
  background-image: 
    repeating-linear-gradient(
      0deg,
      rgba(0, 0, 0, 0.15),
      rgba(0, 0, 0, 0.15) 1px,
      transparent 1px,
      transparent 2px
    );
}
  1. themed (match color scheme):

Use GitHub, Dracula, Nord, or other popular terminal themes.

Code Display & Syntax Highlighting

Shiki (Primary Choice)

Use Shiki for server-side syntax highlighting with TextMate grammars.

import { codeToHtml } from 'shiki';

async function highlightCode(code: string, lang: string) {
  return await codeToHtml(code, {
    lang,
    theme: 'github-dark', // or 'github-light', 'dracula', 'nord', etc.
  });
}

// In component
<div dangerouslySetInnerHTML={{ __html: highlightedCode }} />

Code Block Design

.code-block {
  background: #0d1117;
  border-radius: 8px;
  overflow: hidden;
  margin: 1.5rem 0;
  box-shadow: var(--shadow-lg);
  font-family: 'Zen Mono', monospace;
}

.code-header {
  background: #161b22;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid #30363d;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.code-language {
  font-size: 0.75rem;
  color: #8b949e;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.code-content {
  padding: 1rem;
  overflow-x: auto;
  font-size: 14px;
  line-height: 1.6;
}

/* Line numbers */
.code-line {
  display: table-row;
}

.code-line-number {
  display: table-cell;
  padding-right: 1rem;
  color: #6e7781;
  text-align: right;
  user-select: none;
  width: 1%;
}

.code-line-content {
  display: table-cell;
}

Copy Button

function CopyButton({ code }: { code: string }) {
  const [copied, setCopied] = useState(false);

  const copyCode = async () => {
    await navigator.clipboard.writeText(code);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  return (
    <button 
      onClick={copyCode}
      className="copy-button"
      aria-label="Copy code"
    >
      {copied ? <Check size={16} /> : <Copy size={16} />}
      {copied ? 'Copied!' : 'Copy'}
    </button>
  );
}

Line Highlighting

Highlight specific lines for emphasis:

.code-line-highlighted {
  background: rgba(255, 255, 0, 0.1);
  border-left: 3px solid #f9c74f;
  padding-left: calc(1rem - 3px);
}

Semantic Highlighting

Go beyond syntax: highlight meaning.

Use cases:

// Using tree-sitter or LSP for semantic analysis
function applySemanticHighlighting(tokens: Token[]) {
  return tokens.map(token => {
    if (token.type === 'variable.unused') {
      return {
        ...token,
        style: 'opacity: 0.5; text-decoration: line-through;'
      };
    }
    if (token.type === 'function.definition') {
      return {
        ...token,
        style: 'font-weight: 600;'
      };
    }
    if (token.type === 'variable.parameter') {
      return {
        ...token,
        style: 'font-style: italic;'
      };
    }
    return token;
  });
}

Best Practices

Do:

Don't: