Interactive Terminal Components
Beautiful, customizable terminal simulations for your web applications. Engage users with interactive command-line experiences.
Hacker Terminal
Create immersive cybersecurity simulations
Installation Guide
1
Install Dependencies
Lucide React
npm install lucide-react
2
Copy Component Code
Interactive Terminal.tsx
TypeScript1"use client";
2import React, { useState, useEffect, useRef, useCallback } from "react";
3import { Command, Send, Copy, RotateCcw } from "lucide-react";
4
5export type TerminalProps = {
6 bgColor?: string;
7 textColor?: string;
8 command?: string;
9 commandBg?: string;
10 commandMessage?: string;
11 processingSteps?: string[];
12 finalMessage?: string;
13 stepDelay?: number;
14 typingDelay?: number;
15 icon?: React.ReactNode;
16 promptSymbol?: string;
17 inputPlaceholder?: string;
18 outputHeight?: string;
19 rounded?: string;
20 className?: string;
21 autoMode?: boolean;
22 repeat?: boolean;
23 repeatDelay?: number;
24};
25
26const InteractiveTerminal: React.FC<TerminalProps> = ({
27 bgColor = "bg-gray-900",
28 textColor = "text-green-400",
29 command = "help",
30 commandBg = "bg-gray-950",
31 rounded = "sm",
32 commandMessage = "Enter this command:",
33 processingSteps = ["Processing command..."],
34 finalMessage = "Command executed successfully!",
35 stepDelay = 1000,
36 typingDelay = 100,
37 icon = <Command className="mr-2" />,
38 promptSymbol = "$",
39 inputPlaceholder = "Type your command here...",
40 outputHeight = "h-80",
41 autoMode = false,
42 repeat = false,
43 repeatDelay = 3000,
44}) => {
45 const [input, setInput] = useState("");
46 const [output, setOutput] = useState<string[]>([]);
47 const [step, setStep] = useState(0);
48 const [copied, setCopied] = useState(false);
49 const [typing, setTyping] = useState(false);
50 const [charIndex, setCharIndex] = useState(0);
51 const outputRef = useRef<HTMLDivElement>(null);
52 const [commandExecuted, setCommandExecuted] = useState(false);
53 const [completed, setCompleted] = useState(false);
54
55 const resetTerminal = useCallback(() => {
56 setOutput([]);
57 setStep(0);
58 setCharIndex(0);
59 setTyping(false);
60 setCommandExecuted(false);
61 setCompleted(false);
62 }, []);
63
64 const executeCommand = useCallback(() => {
65 setOutput((prev) => [...prev, `${promptSymbol} ${input}`]);
66 setStep(1);
67 setInput("");
68 }, [promptSymbol, input]);
69
70 useEffect(() => {
71 if (outputRef.current) {
72 outputRef.current.scrollTop = outputRef.current.scrollHeight;
73 }
74 }, [output]);
75
76 useEffect(() => {
77 if (autoMode && !typing && !commandExecuted) {
78 const timer = setTimeout(() => {
79 setTyping(true);
80 setCharIndex(0);
81 }, 500);
82 return () => clearTimeout(timer);
83 }
84 }, [autoMode, typing, commandExecuted]);
85
86 useEffect(() => {
87 if (autoMode && repeat && completed) {
88 const repeatTimer = setTimeout(() => {
89 resetTerminal();
90 }, repeatDelay);
91 return () => clearTimeout(repeatTimer);
92 }
93 }, [autoMode, repeat, completed, resetTerminal, repeatDelay]);
94
95 useEffect(() => {
96 if (typing && charIndex < command.length) {
97 const timer = setTimeout(() => {
98 setInput(command.substring(0, charIndex + 1));
99 setCharIndex(charIndex + 1);
100 }, typingDelay);
101 return () => clearTimeout(timer);
102 } else if (typing && charIndex === command.length) {
103 const timer = setTimeout(() => {
104 executeCommand();
105 setTyping(false);
106 setCommandExecuted(true);
107 }, 500);
108 return () => clearTimeout(timer);
109 }
110 }, [typing, charIndex, command, typingDelay, executeCommand]);
111
112 useEffect(() => {
113 if (step > 0 && step <= processingSteps.length) {
114 setOutput((prev) => [...prev, processingSteps[step - 1]]);
115 const timer = setTimeout(() => setStep(step + 1), stepDelay);
116 return () => clearTimeout(timer);
117 } else if (step > processingSteps.length) {
118 setOutput((prev) => [...prev, finalMessage]);
119 setCompleted(true);
120 }
121 }, [step, processingSteps, finalMessage, stepDelay]);
122
123 const handleSubmit = (e: React.FormEvent) => {
124 e.preventDefault();
125 executeCommand();
126 setCommandExecuted(true);
127 };
128
129 const copyCommand = () => {
130 navigator.clipboard.writeText(command);
131 setCopied(true);
132 setTimeout(() => setCopied(false), 2000);
133 };
134
135 const replayCommand = () => {
136 resetTerminal();
137 };
138
139 return (
140 <div
141 className={`max-w-4xl mx-auto p-3 md:p-6 ${bgColor} ${textColor} rounded-${rounded} shadow-lg font-mono scrollbar-thin`}
142 >
143 <div
144 className={`mb-4 p-2 ${commandBg} border-${textColor} rounded flex items-center justify-between`}
145 >
146 <div className="flex items-center gap-1">
147 <div className="-mt-1">{icon}</div>
148 <span>
149 {commandMessage} <strong>{command}</strong>
150 </span>
151 </div>
152 <div className="flex gap-2">
153 {autoMode ? (
154 completed &&
155 !repeat && (
156 <button
157 onClick={replayCommand}
158 className={`px-2 py-1 ${textColor} rounded text-sm flex items-center cursor-pointer`}
159 title="Replay"
160 type="button"
161 >
162 <RotateCcw className="w-4 h-4 mr-1" />
163 Replay
164 </button>
165 )
166 ) : step === 0 ? (
167 <button
168 onClick={copyCommand}
169 className={`px-2 py-1 ${textColor} rounded text-sm flex items-center cursor-pointer`}
170 title="Copy command"
171 type="button"
172 >
173 <Copy className="w-4 h-4 mr-1" />
174 {copied ? "Copied!" : "Copy"}
175 </button>
176 ) : (
177 <button
178 onClick={resetTerminal}
179 className={`px-2 py-1 ${textColor} rounded text-sm flex items-center cursor-pointer`}
180 title="Reset terminal"
181 type="button"
182 >
183 <RotateCcw className="w-4 h-4 mr-1" />
184 Reset
185 </button>
186 )}
187 </div>
188 </div>
189 <div
190 ref={outputRef}
191 className={`${outputHeight} mb-4 p-2 bg-black rounded hide-scrollbar overflow-y-auto`}
192 >
193 {output.map((line, index) => (
194 <pre key={index} className="whitespace-pre-wrap">
195 {line}
196 </pre>
197 ))}
198 {typing && (
199 <pre className="whitespace-pre-wrap cursor-typing">
200 {promptSymbol} {input}
201 </pre>
202 )}
203 {autoMode && repeat && completed && (
204 <pre className="text-gray-500 italic mt-2">
205 Repeating command in {Math.ceil(repeatDelay / 1000)} seconds...
206 </pre>
207 )}
208 </div>
209 {!autoMode && step === 0 && !commandExecuted && (
210 <form onSubmit={handleSubmit} className="flex items-center">
211 <span className="mr-2">{promptSymbol}</span>
212 <input
213 type="text"
214 value={input}
215 onChange={(e) => setInput(e.target.value)}
216 className="flex-grow bg-transparent focus:outline-none cursor-typing"
217 placeholder={inputPlaceholder}
218 />
219 <button
220 type="button"
221 onClick={executeCommand}
222 className="ml-2 p-1 rounded-full hover:bg-gray-700 transition-colors cursor-pointer"
223 title="Send command"
224 >
225 <Send className="w-4 h-4" />
226 </button>
227 </form>
228 )}
229 <style jsx>{`
230 @keyframes blink {
231 0% {
232 opacity: 0;
233 }
234 50% {
235 opacity: 1;
236 }
237 100% {
238 opacity: 0;
239 }
240 }
241 .cursor-typing::after {
242 content: "|";
243 animation: blink 1s infinite;
244 }
245 pre {
246 animation: fadeIn 0.5s ease-in-out;
247 }
248 @keyframes fadeIn {
249 from {
250 opacity: 0;
251 }
252 to {
253 opacity: 1;
254 }
255 }
256 .hide-scrollbar::-webkit-scrollbar {
257 display: none !important;
258 }
259 .hide-scrollbar {
260 -ms-overflow-style: none !important;
261 scrollbar-width: none !important;
262 }
263 `}</style>
264 </div>
265 );
266};
267
268export default InteractiveTerminal;
269
3
Final Steps
Update Import Paths
Make sure to update the import paths in the component code to match your project structure. For example, change @/components/ui/button
to match your UI components location.
Props
Name | Type | Default | Description |
---|---|---|---|
autoMode | boolean | false | Enables automatic command execution. |
bgColor | string | "bg-gray-900" | Background color for the terminal container. |
textColor | string | "text-green-400" | Text color for the terminal output. |
command | string | "help" | Command that users need to enter to trigger the terminal sequence. |
commandBg | string | "bg-gray-950" | Background color for the command info bar. |
rounded | string | "sm" | Border radius for the terminal container. |
commandMessage | string | "Enter this command:" | Message to display about the command. |
processingSteps | string[] | ["Processing command..."] | Array of processing steps to display sequentially. |
finalMessage | string | "Command executed successfully!" | Final message to display after all processing steps. |
stepDelay | number | 1000 | Delay between processing steps in milliseconds. |
title | string | "Interactive Terminal" | Title displayed above the terminal. |
icon | React.ReactNode | <TerminalIcon className="mr-2" /> | Icon to display in the command info bar. |
promptSymbol | string | "$" | Terminal prompt symbol displayed before user input. |
inputPlaceholder | string | "Type your command here..." | Placeholder text for the input field. |
outputHeight | string | "h-80" | Height for the terminal output area. |
className | string | "" | Custom class names to apply to the container. |
backgroundGradient | string | "bg-gray-100" | Background gradient for the container. |
Examples
Love Terminal
Spread positivity with charming messages
Deployment Terminal
Visualize your CI/CD deployment process
Coffee Order Terminal
Showcase products with fun interactions