mischief
exampleterminal-output-demosource: ok · refreshed
Terminal Output Demo
A worked example of Terminal Output: Streaming command output with stderr called out, an exit code, and scroll that follows without trapping you.
npx shadcn add "https://ui.tinkererslabs.com/r/terminal-output-demo.json"free
Visual style
terminal-mono
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightlucide-react@^1.31.0
Registry dependencies
https://ui.tinkererslabs.com/r/terminal-output.json
Files3 · source cached 10/7/2026, 12:37:36 AM
components/demos/terminal-output-demo.tsx
"use client"
import * as React from "react"
import { DemoVariants } from "@/components/demos/demo-variants"
import { RestartButton } from "@/components/demos/restart-button"
import {
TerminalOutput,
type TerminalLine,
} from "@/registry/default/terminal-output/terminal-output"
const script: TerminalLine[] = [
{ text: "Packages: +214" },
{ text: "Progress: resolved 214, reused 210, downloaded 4" },
{ text: "" },
{ text: "dependencies:" },
{ text: "+ mischief-ui 0.7.1" },
{ text: "" },
{ text: "Done in 1.8s" },
]
const failure: TerminalLine[] = [
{
text: "src/total.ts(4,17): error TS2554: Expected 1 arguments, but got 2.",
stream: "stderr",
},
{ text: "", stream: "stderr" },
{ text: "Found 1 error in src/total.ts", stream: "stderr" },
]
function LiveRun() {
const [count, setCount] = React.useState(0)
const [runId, setRunId] = React.useState(0)
React.useEffect(() => {
if (count >= script.length) return
const timer = setTimeout(() => setCount((shown) => shown + 1), 420)
return () => clearTimeout(timer)
}, [count, runId])
const done = count >= script.length
return (
<div className="grid gap-4">
<TerminalOutput
command="pnpm install mischief-ui"
cwd="~/projects/shop"
output={script.slice(0, count)}
running={!done}
exitCode={done ? 0 : undefined}
maxHeight="11rem"
/>
{done ? (
<RestartButton
onClick={() => {
setCount(0)
setRunId((id) => id + 1)
}}
/>
) : null}
</div>
)
}
export function TerminalOutputDemo() {
return (
<DemoVariants
label="Command run"
variants={[
{ id: "live", label: "Streaming", render: () => <LiveRun /> },
{
id: "failed",
label: "Failed",
render: () => (
<TerminalOutput
command="pnpm typecheck"
output={failure}
exitCode={2}
maxHeight="11rem"
/>
),
},
]}
/>
)
}
components/demos/demo-variants.tsx
"use client"
import * as React from "react"
export type DemoVariant = {
id: string
label: string
render: () => React.ReactNode
}
export function DemoVariants({
variants,
label = "Demo variant",
}: {
variants: [DemoVariant, ...DemoVariant[]]
label?: string
}) {
const [activeId, setActiveId] = React.useState(variants[0].id)
const active =
variants.find((variant) => variant.id === activeId) ?? variants[0]
return (
<div className="grid w-full justify-items-center gap-6">
{/* Centres whatever the variant renders. Without this a demo that caps
its own width sits against the left edge while the switch below it
is centred, which reads as a mistake. */}
<div className="flex w-full justify-center">{active.render()}</div>
<div
role="group"
aria-label={label}
className="border-border bg-background/70 inline-flex flex-wrap justify-center gap-1 rounded-full border p-1"
>
{variants.map((variant) => {
const isActive = variant.id === active.id
return (
<button
key={variant.id}
type="button"
aria-pressed={isActive}
className={
isActive
? "bg-foreground text-background rounded-full px-3 py-1.5 text-xs font-semibold"
: "text-muted-foreground hover:text-foreground rounded-full px-3 py-1.5 text-xs font-semibold"
}
onClick={() => setActiveId(variant.id)}
>
{variant.label}
</button>
)
})}
</div>
</div>
)
}
components/demos/restart-button.tsx
"use client"
import { RotateCcw } from "lucide-react"
export function RestartButton({ onClick }: { onClick: () => void }) {
return (
<button className="restore-button" type="button" onClick={onClick}>
<RotateCcw aria-hidden="true" size={15} /> Replay
</button>
)
}