Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
  1. Home
  2. Blog
  3. Component guides
  4. React Terminal Component: Typing Animation Without Jank
Auf Deutsch lesen
  1. Blog
  2. Component guides
  3. React Terminal Component: Typing Animation Without Jank
Component guides
Component guides

React Terminal Component: Typing Animation Without Jank

SR
Serban Rusu · Founder of Wingo UI
Nov 6, 2026 · 8 min read

On this page

0%
  1. What should a React terminal component do?
  2. How do you add a terminal to a React app?
  3. How do you show a failing step?
  4. How does a React terminal animation avoid jank?
  5. What happens with reduced motion and screen readers?
  6. How does the terminal behave on a phone?
  7. How do you control playback from your own button?
  8. What terminal components exist for shadcn projects?
  9. When is a code block or a text animation the better choice?
  10. Where does a terminal fit on a landing page?
  11. Next step
  12. FAQ
    1. How do I make an animated terminal in React?
    2. Does shadcn/ui have a terminal component?
    3. Is an animated terminal bad for accessibility?
    4. Should I use a terminal or a code block for install commands?
    5. Is the Wingo UI Terminal free?

TL;DR

A good React terminal component types each command after a prompt, runs a spinner or progress bar, prints colored output, and lets people copy the commands and replay the session. It should lay out the whole transcript from the first frame so the page never shifts, keep the text readable for screen readers and search engines, and show the finished session at once when reduced motion is on. Wingo UI's Terminal (a Pro component) does all of that from one lines array and drives the typing with a single motion value, so React does not re-render once per character.

Published Nov 6, 2026

A React terminal component is the window on a dev-tool landing page where npm install types itself, a spinner turns into a check and the deploy URL prints in green. It shows what a CLI does faster than a paragraph can, and it is easy to get wrong: the hero jumps as lines appear, a screen reader hears nothing, the animation replays forever for people who asked for less motion, and a long command pushes the page sideways on a phone. This guide builds a React terminal UI that avoids all four with the Terminal from Wingo UI. We build that library, so we are not neutral; facts about other projects link to their sources and are dated.

What should a React terminal component do?

Show a believable session, stay out of the way and stay readable. Here is the list we built against:

  • Typed commands after a prompt, with a block caret, at a speed you can set.
  • Activity: a spinner that settles into a check (or a cross when the step failed) and a progress bar that fills to 100%.
  • Toned output: dimmed by default, with single lines in success, warning or danger colors.
  • Copy and replay buttons, so nobody retypes a command or reloads the page to watch the session again.
  • No layout shift. The window has its final height before the first character types.
  • Readable without JavaScript. The whole transcript is in the server HTML, for crawlers and screen readers alike.
  • Reduced motion shows the finished session at once.
  • Phone behavior: long lines scroll inside the window, never the page.

How do you add a terminal to a React app?

Install it with the CLI. It copies terminal.tsx into components/ui/ along with what it imports (Button, Copy Button, Tooltip and the motion helpers). Terminal is a Pro item, so log in first with npx wingo-ui@latest login:

bash
npx wingo-ui@latest add terminal

Then describe the session as data. Each entry in lines is one step: a command to type, an optional spinner or progress label that runs for duration ms, and output lines as strings or { text, tone }.

tsx
import { Terminal, type TerminalLine } from "@/components/ui/terminal"
const lines: TerminalLine[] = [
{
command: "npm install @acme/cli",
spinner: "Installing @acme/cli",
duration: 1300,
output: [{ text: "✓ Added 1 package in 1.9s", tone: "success" }],
},
{
command: "npx acme deploy --prod",
progress: "Uploading",
duration: 1600,
output: [
"Build 6.1s 214 files",
"Region us-east-1 (New York)",
{ text: "✓ Live at https://northwind.acme.app", tone: "success" },
],
},
]
export function DeployHero() {
return (
<Terminal
lines={lines}
title="emma@macbook: ~/northwind"
mode="dark"
trafficLights
className="max-w-xl"
/>
)
}

Scroll the demo into view to watch it play, then press replay. Try the copy button too: it copies both commands, one per line.

Watch the session type itself, then press replay to run it again or copy to grab both commands
$ npx wingo-ui@latest add terminal
ProTerminal docs

The props that matter most: mode="dark" keeps the window dark on a light page (the classic hero look), chrome picks mac dots, a plain title bar or no chrome at all, tone or any CSS color paints the prompt, caret and progress bars, and prompt changes $ to ❯ or root@vps:~#. A step can set its own prompt and delay. If the copy button should copy something other than the commands, such as a one-line install script, pass it as copyValue.

How do you show a failing step?

Set tone: "danger" on the step. The spinner ends in a cross instead of a check, and the output lines can each carry their own tone:

tsx
const tests: TerminalLine[] = [
{
command: "npm test",
spinner: "Running 48 tests",
tone: "danger",
duration: 1500,
output: [
{ text: "✓ 46 passed", tone: "success" },
{ text: "! 1 skipped checkout/apple-pay.test.ts", tone: "warning" },
{ text: "✗ 1 failed cart/totals.test.ts", tone: "danger" },
" Expected $128.40, received $128.04",
],
},
]

A red line in a marketing terminal sounds odd, but it works well in docs for a "what an error looks like" section, and in a changelog that shows a bug before and after.

How does a React terminal animation avoid jank?

Keep React out of the per-character loop. The obvious way to build an animated terminal is a setInterval that appends one character to state. Magic UI's TypingAnimation, for example, starts from an empty string and calls setDisplayedText once per character, 60 ms apart by default (registry source (opens in a new tab), checked November 6, 2026). That is fine for one headline. A whole session of commands means a render every 45 to 60 ms for several seconds, and the text is not in the DOM until it has typed.

Wingo UI's Terminal works the other way around. The full transcript is laid out on the first render. One motion value holds the session clock in milliseconds, and every row derives its own state from it: a command reveals itself with a clip-path that grows one ch at a time, the caret moves with a transform, and output rows switch their opacity at their scheduled time. React renders only when playback starts and ends. Because every row already takes its final space, the window keeps the same height from the first frame to the last, so the content below it never shifts.

Two more rules keep it calm. With trigger="in-view" (the default) it waits until about a third of the window is on screen, so a terminal below the fold does not finish before anyone sees it. With trigger="mount" it starts as soon as the page has hydrated. With loop, it rests while it is off screen instead of burning frames.

What happens with reduced motion and screen readers?

Under prefers-reduced-motion: reduce, the Terminal shows the finished transcript at once and disables the replay button, since there is nothing to replay. It reads the preference through usePrefersReducedMotion from lib/motion, which is hydration safe; we explain why motion's own hook is not in Prefers reduced motion in React. animate={false} gives the same still result on purpose, for example in a thumbnail or a printed docs page.

For screen readers, the transcript is a focusable region named by the title (or labels.region, "Terminal" by default). Rows that are not visible yet still contain their text, so the whole session is read in order. Prompts, dots, the caret and the spinner frames are aria-hidden, so a reader hears the commands and the output, not "dollar" and braille dots. The copy button announces "Copied" through the Copy Button it is built on.

Cult UI made a different, reasonable choice: its Terminal Animation marks the output role="log" with aria-live="polite", so new lines are announced as they appear (docs (opens in a new tab), checked November 6, 2026). That suits output a user triggered. For a hero that plays on its own, we preferred silence over a reader announcing a fake deploy line by line.

How does the terminal behave on a phone?

Lines never wrap, because a wrapped shell command looks broken and copies badly. Instead the viewport scrolls sideways inside the window, with overscroll-x-contain so a swipe does not drag the page, and keyboard users can Tab to it and use the arrow keys. Below 640px the type steps down one size (12px becomes 11px for size="sm", 13px becomes 12px for md), which fits a typical npm install line on a 390px screen. The copy and replay buttons are regular Button components at the small icon size, which keeps a 32px look and adds a 44px hit area on a coarse pointer.

One pitfall: the typing reveal counts characters in ch, the width of one monospace character. Keep emoji and other wide characters out of command strings; they are fine in output, which does not type.

How do you control playback from your own button?

Pass playing and onPlayingChange. With defaultPlaying={false} the window waits on an empty prompt until you start it, and onComplete fires every time a session finishes:

tsx
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Terminal, type TerminalLine } from "@/components/ui/terminal"
export function RunDeploy({ lines }: { lines: TerminalLine[] }) {
const [playing, setPlaying] = useState(false)
return (
<div className="flex flex-col gap-3">
<Terminal
lines={lines}
defaultPlaying={false}
playing={playing}
onPlayingChange={setPlaying}
showReplay={false}
/>
<Button variant="soft" onClick={() => setPlaying((value) => !value)}>
{playing ? "Pause" : "Run deploy"}
</Button>
</div>
)
}

onPlayingChange(false) arrives at the end of a session that does not loop, so the button label flips back on its own.

What terminal components exist for shadcn projects?

shadcn/ui itself has none, so a shadcn terminal component comes from a third-party registry or from npm. What their docs, source and npm pages showed on November 6, 2026:

ComponentInstallSpinner / progressCopy, replayReduced motion
Magic UI Terminal (opens in a new tab)shadcn registry, @magicui/terminalNoNoNot handled in the source
Cult UI Terminal Animation (opens in a new tab)shadcn registry, @cult-ui/terminal-animationNoA refresh button in the demo, no copyNot mentioned in the docs
react-animated-term (opens in a new tab)npm, last release 2.0.0 (February 2021)Spinners through framesNoNot mentioned
Wingo UI Terminalnpx wingo-ui@latest add terminalBothBothShows the finished transcript

Where the others are the better pick: Magic UI and Cult UI are free and MIT licensed, and the Wingo UI Terminal is not. Magic UI composes any JSX as a line, which is handy for custom output. Cult UI has tabbed scenarios built in (npm, pnpm and yarn tabs, say) and a background image option. react-animated-term is a plain npm dependency with no registry, if you can live with a package that has not shipped a release since 2021.

When is a code block or a text animation the better choice?

When the reader should act on the text. An animated terminal is a demo; the steps of a setup guide belong in a static, highlighted Code Block with a copy button, which is free:

tsx
import { CodeBlock } from "@/components/ui/code-block"
export function InstallStep() {
return <CodeBlock code="npx wingo-ui@latest add terminal" language="bash" filename="Terminal" showLineNumbers={false} />
}
A highlighted snippet with a filename header and a copy button: select the text or press copy
$ npx wingo-ui@latest add code-block
FreeCode Block docs

And when you only want a headline to type itself, the full window is too much. Text Animate with effect="typewriter" types one string with a caret, keeps the element a real h1 through as, and reads the full sentence to screen readers once.

When the output is real, streamed from a build or a server, skip the animation altogether. Our React log viewer guide shows live lines with level filters and a follow mode instead.

Where does a terminal fit on a landing page?

In the hero, next to the headline, or in a "how it works" band right under it. Keep it to two or three steps; a session that runs longer than about eight seconds loses the reader before the payoff line. Put the real install command in a code block or a copy pill nearby, so nobody has to wait for the animation to copy it. Our shadcn hero section guide covers the layouts around it.

If you are building a whole developer platform page, the Developers Page template already has a code hero with language tabs, API surface cards, an endpoint explorer and a CLI terminal that plays deploy and webhook lines when it scrolls into view.

More guides like this one are in the component guides category, starting with the pillar on React form components.

Next step

Open the Terminal docs and try chrome, tone, speed and loop in the playground, then install it with npx wingo-ui@latest add terminal. Terminal and the Developers Page are part of Wingo UI Pro (pricing); the Code Block and Copy Button are free with a free account.

Components in this post

  • Terminal

    An animated React terminal window for dev-tool landing pages and docs: typed commands, output, spinners and progress bars.

    Pro
  • Code Block

    A React code block with syntax highlighting, line numbers, highlighted lines and a copy button, themed from a single accent color.

    Free
  • Copy Button

    A React copy to clipboard button for IBANs, tax IDs, links and API keys: an icon button with a tooltip, a labelled button and CopyText.

    Free
  • Text Animate

    Animated text for headlines and copy that reveals by word, character or line with a fade, blur, slide, scale or typewriter effect.

    Pro
  • Developers Page

    A React developer platform page template: a code hero with language tabs, API surfaces, a live CLI terminal, endpoints, uptime and limits.

    Pro

FAQ

How do I make an animated terminal in React?

Describe the session as data (each step a command, optional spinner or progress bar, and output lines) and let a component play it on a timeline. With Wingo UI that is npx wingo-ui@latest add terminal and a lines array passed to the Terminal component.

Does shadcn/ui have a terminal component?

No. As of November 6, 2026, the shadcn/ui component list has no terminal, while Magic UI and Cult UI publish terminal components through the shadcn registry and Wingo UI installs its Terminal as source code through its own CLI.

Is an animated terminal bad for accessibility?

It can be. Text that builds up one character at a time is announced in fragments or not at all, and motion bothers some readers. Render the full text from the start, reveal it visually only, and skip the animation under prefers-reduced-motion.

Should I use a terminal or a code block for install commands?

Use a code block when people should read and copy a command, and a terminal when you want to show what happens after they run it. A docs page can use both: the code block in the setup steps, the terminal in the hero.

Is the Wingo UI Terminal free?

No, Terminal is part of Wingo UI Pro ($8 a month, $80 a year or $150 once). The Code Block and Copy Button it pairs with are free with a free account.

Try it in your project

Terminal

Pro

An animated React terminal window for dev-tool landing pages and docs: typed commands, output, spinners and progress bars.

75 components free, no account needed. Pro from $8 a month.

Open Terminal liveSee pricing
  • Components
  • React
  • Animation
  • Landing page
  • Docs

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Component guides

React Diff Viewer in React 19: Split, Unified, Word Diffs

How a React diff viewer works, which packages still support React 19, and how to review AI edits, contracts and configs with split views and word highlights.

SRSerban Rusu·Oct 16, 2026·9 min read
Component guides

React JSON Viewer: Search, Copy Paths and Big Payloads

A React JSON viewer for webhooks and admin screens: search that opens the matching branches, copy a value or its path, a phone layout and 10,000-item arrays.

SRSerban Rusu·Oct 16, 2026·9 min read
Component guides

IBAN Validation JavaScript: Mod 97, Regex and a React Field

IBAN validation JavaScript that catches typos: mod 97 in TypeScript, why a regex is not enough, a React field that formats as you type and Romanian banks.

SRSerban Rusu·Oct 9, 2026·12 min read
Newsletter

Get new posts by email

New posts, product news and occasional offers from Wingo UI. We count clicks to keep it relevant.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • For teams
  • Theme generator

Resources

  • Docs
  • Contact
  • Programs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Compare

  • Wingo UI vs paid kits
  • Wingo UI vs shadcn/ui
  • shadcn/ui alternatives
  • MUI alternatives
  • Tailwind Plus alternatives
  • Magic UI alternatives
  • Free components

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI