Componenteditors1.01 KB gzipZero dependencies

Terminal

Fake terminal card with sequential line reveal and blinking cursor

Preview

Live Preview
terminal
✓ All systems nominal

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add terminal

Or install the full npm package:

npm install bigbullui
import { Terminal } from "@/components/ui/terminal";

<Terminal lines={lines} loop={true} />

Usage guidance

Use Terminal when a ticket stub surface needs Fake terminal card with sequential line reveal and blinking cursor Keep props minimal and prefer composition with sibling stubs.

Avoid Terminal for long form flows or dense data grids where a dedicated layout block fits better.

Source code

terminal.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export interface TerminalLine {
  text: string;
  tone?: "info" | "warn" | "error";
}

export interface TerminalProps extends React.HTMLAttributes<HTMLDivElement> {
  lines: TerminalLine[];
  prompt?: string;
  loop?: boolean;
  className?: string;
}

export function Terminal({
  lines,
  prompt = "> ",
  loop = false,
  className,
  ...props
}: TerminalProps) {
  const [visibleIndex, setVisibleIndex] = React.useState(0);

  return (
    <div
      className={cn(
        "w-full rounded-lg border border-border/60 bg-card overflow-hidden",
        "motion-reduce:animate-none",
        className,
      )}
      {...props}
    >
      {/* Title bar */}
      <div className="flex items-center border-b border-border/60 bg-black/20 p-2">
        <div className="w-full flex items-center gap-2">
          <span className="w-2 h-2 rounded-full bg-success"></span>
          <span className="w-2 h-2 rounded-full bg-success"></span>
          <span className="w-2 h-2 rounded-full bg-success"></span>
          <span className="text-[10px] uppercase text-muted-foreground">terminal</span>
        </div>
        <div className="ms-auto flex items-center gap-1">
          <span className="w-2 h-2 rounded-full bg-gray-300"></span>
          <span className="w-2 h-2 rounded-full bg-gray-400"></span>
        </div>
      </div>

      {/* Output area */}
      <div
        className="h-64 overflow-y-auto p-4 font-mono text-[10px] leading-relaxed text-muted-foreground"
        role="log"
      >
        {lines.map((line, lineIdx) => {
          const delay = lineIdx * 40;

          return (
            <div
              key={line.text}
              className={cn(
                "motion-reduce:transition-none",
                `animate-[type-in_0.2s_ease-out_both ${delay}ms fill mode]`,
                lineIdx < visibleIndex ? "" : "hidden",
              )}
            >
              <span className="font-mono break-all">{line.text}</span>
            </div>
          );
        })}

        {/* Auto-scroll: show last line */}
        {lines.length > 0 && (
          <div
            style={{ opacity: 0 }}
            className="pt-2"
          >
            {lines[lines.length - 1]?.text}
          </div>
        )}
      </div>

      {/* Input area */}
      <div className="p-2 border-t border-border/60 bg-black/10">
        <div className="flex gap-2">
          <input
            type="text"
            className={cn(
              "flex-1 rounded-b-lg border border-border/60 bg-black p-2 text-[10px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
            )}
            placeholder=">"
          />
          <button
            className={cn(
              "rounded-b-lg border border-border/60 bg-black px-3 text-[10px]",
            )}
            onClick={() => {}}
          >
            ↩
          </button>
        </div>
      </div>
    </div>
  );
}

Props

PropTypeDescription
linesTerminalLine[]
prompt?string
loop?boolean
className?string