June 24, 2025
O. Wolfson
When creating user interfaces, even small touches can dramatically affect how users perceive the responsiveness and friendliness of your app. One subtle but powerful UX enhancement is typed-out responses, instead of showing content instantly.
In this article, we’ll walk through building a simple but effective Command Line Interface (CLI)-style React component that responds to hardcoded commands with a smooth, context-aware typing animation — perfect for portfolios, chatbot experiments, or terminal-style playgrounds.
A React component that:
help, about, clear, short, medium, long)history StateStores all terminal lines — each one tagged as user input or system output.
tstype Line = { type: "input" | "output"; content: string };
This forms the scrollable history log you see in the console.
input StateTracks the current value of the user's text input.
pendingOutput and isTypingControl the output animation phase. When a command is submitted, the corresponding response is added to pendingOutput, and the typing logic begins.
To avoid long delays for lengthy messages, the component adjusts how it renders responses based on their length:
| Message Length | Mode | Behavior | Speed |
|---|---|---|---|
| ≤ 200 chars | char | Renders letter by letter | 20ms/tick |
| 201–500 chars | word | Renders word by word | 50ms/tick |
| > 500 chars | sentence | Renders sentence by sentence | 150ms/tick |
This keeps the animation effect pleasant without making long messages feel sluggish.
tsuseEffect(() => {
const mode = getTypingMode(pendingOutput); // "char" | "word" | "sentence"
const parts = splitByMode(pendingOutput, mode); // split string by units
let index = 0;
const interval = setInterval(() => {
setHistory((prev) => {
const newContent = parts
.slice(0, index + 1)
.join(mode === "char" ? "" : " ");
const updated = [...prev];
const last = updated[updated.length - 1];
if (last?.type === "output") {
updated[updated.length - 1].content = newContent;
} else {
updated.push({ type: "output", content: newContent });
}
return updated;
});
index++;
if (index >= parts.length) {
clearInterval(interval);
setIsTyping(false);
setPendingOutput("");
}
}, getDelayForMode(mode));
}, [isTyping, pendingOutput]);
tsconst RESPONSES = {
help: `Available commands:\n- help\n- about\n- contact\n- clear\n- short\n- medium\n- long`,
about: `This is a simple demonstration of a command console component that uses a typewriter-style animation to render responses.
Typing animations can greatly enhance perceived interactivity and responsiveness in user interfaces, even in basic terminal-like environments.
This demo is written in React using functional components and hooks. It can easily be extended with additional commands, real-time data fetching, or even connected to a backend.
Try typing 'help' to see available commands, or 'clear' to reset the console.`,
contact: `Email: contact@example.com\nTwitter: @example`,
short: `This is short.`,
medium: `This is a medium-length message designed to demonstrate how each word appears one after the other with smooth timing, simulating a thoughtful typing pace.`,
long: `This is a long-form demonstration designed to show how sentence-based rendering improves readability and performance.
When a message exceeds a certain length, typing each individual character becomes tedious and unnecessary. Instead, we chunk the message into sentences and display them with a natural delay.
This balances responsiveness with user experience, especially in UIs where content is meant to be skimmed or understood quickly, like help messages or documentation previews.`,
};
clearTyping clear wipes the console history instantly:
tsif (command === "clear") {
setHistory([]);
return;
}
clear is usedYou could easily expand this component with:
This enhanced command console is a lightweight yet powerful tool for adding retro-flavored interactivity to any web app. By dynamically adjusting how responses are rendered, you get a UX that feels responsive for short replies and efficient for long-form content.
Perfect use cases: