Terminal Text
refinery/terminal-textFreeComponent
Typewriter-style terminal text loop with blinking cursor and optional prefix.
Live preview
live · rendered by the registry
Rendered by refinery on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://refinery.abhii.me/r/terminal-text.jsonSource
1"use client";23import { useEffect, useState } from "react";4import { motion } from "motion/react";5import { cn } from "@/lib/utils";67const TYPE_SPEED = 55;8const DELETE_SPEED = 28;9const HOLD_TIME = 850;10const SWITCH_DELAY = 120;1112export const previewTexts = [13 "build things that matter",14 "simplicity is the ultimate sophistication",15 "ship it. then make it better.",16 "who let the dogs out",17 "woff woff woff woff",18];1920interface TerminalTextProps {21 texts?: string[];22 prefix?: string;23 className?: string;24}2526type TypeState = {27 index: number;28 visibleLength: number;29 isDeleting: boolean;30};3132function getNextState(state: TypeState, textLength: number, totalText: number) {33 const { index, visibleLength, isDeleting } = state;3435 if (!isDeleting && visibleLength < textLength) {36 return {37 next: { ...state, visibleLength: visibleLength + 1 },38 delay: TYPE_SPEED,39 };40 }41 if (!isDeleting && visibleLength === textLength) {42 return { next: { ...state, isDeleting: true }, delay: HOLD_TIME };43 }44 if (isDeleting && visibleLength > 0) {45 return {46 next: { ...state, visibleLength: visibleLength - 1 },47 delay: DELETE_SPEED,48 };49 }50 return {51 next: {52 index: (index + 1) % totalText,53 visibleLength: 0,54 isDeleting: false,55 },56 delay: SWITCH_DELAY,57 };58}5960export function TerminalText({61 texts = previewTexts,62 prefix,63 className = "text-emerald-600 dark:text-emerald-400",64}: TerminalTextProps) {65 const [state, setState] = useState<TypeState>({66 index: 0,67 visibleLength: 0,68 isDeleting: false,69 });7071 const resolvedTexts = texts.length > 0 ? texts : previewTexts;72 const text = resolvedTexts[state.index % resolvedTexts.length];73 const visibleText = text.slice(0, state.visibleLength);7475 useEffect(() => {76 const { next, delay } = getNextState(77 state,78 text.length,79 resolvedTexts.length80 );81 const id = setTimeout(() => setState(next), delay);82 return () => clearTimeout(id);83 }, [state, text, resolvedTexts.length]);8485 return (86 <motion.div87 aria-live="polite"88 className={cn("flex items-center font-mono tracking-[0.08em]", className)}89 >90 <span className="whitespace-nowrap">91 {prefix && <span className="text-current/40">{prefix}</span>}92 {visibleText}93 <motion.span94 aria-hidden="true"95 animate={{ opacity: [1, 0, 1] }}96 transition={{97 duration: 0.9,98 repeat: Infinity,99// … truncated
What it pulls in
npm packages
Files it writes
More from refinery
All 16 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Analog Clock Screensaveranalog-clock-screensaver | refinery | Components | Free | 1 dep | |
| Avatar Circleavatar-circle | refinery | Components | Free | 1 dep | |
| DVD Screensaverdvd-screensaver | refinery | Components | Free | 1 dep | |
| Floating Cardfloating-card | refinery | Components | Free | 1 dep | |
| Floating Navfloating-nav | refinery | Components | Free | 1 dep | |
| Glowy Buttonglowy-button | refinery | Components | Free | 1 dep | |
| Move To Topmove-to-top-button | refinery | Components | Free | 1 dep | |
| Notification Badgenotification-badge | refinery | Components | Free | 1 dep |
