> Building react-terminal-typewriter: A Lightweight React Hook for Terminal-Style Effects
Every developer has seen those cool terminal-style typewriter effects on landing pages. Text that types itself, character by character, creating an engaging and dynamic user experience. I needed one for my projects, but existing solutions were either too heavy, lacked TypeScript support, or didn't handle React 18+ StrictMode properly.
So I built my own: react-terminal-typewriter.
The Problem
When I started looking for a typewriter hook, I found that most solutions had issues:
- Heavy dependencies – many packages pulled in entire animation libraries
- No loop support – text would type once and stop
- React 18 incompatible – StrictMode caused double animations
- Poor TypeScript support – missing or incomplete type definitions
- No cursor control – fixed blink speeds with no customization
I needed something lightweight, type-safe, and flexible.
The Solution
I created react-terminal-typewriter – a single React hook that:
- Has zero dependencies (only React as peer dependency)
- Weighs ~1KB minified
- Supports loop mode with configurable delays
- Handles React 18+ StrictMode correctly
- Provides full TypeScript support
- Offers cursor blink speed control
Quick Start
npm install react-terminal-typewriterimport { useTypewriter } from 'react-terminal-typewriter'
function Hero() {
const { displayText, cursorBlinkSpeed } = useTypewriter({
text: 'Hello, World!',
delay: 100,
loop: true
})
return (
<h1>
{displayText}
<span
className="cursor"
style={{ '--cursor-blink-speed': `${cursorBlinkSpeed}ms` } as React.CSSProperties}
/>
</h1>
)
}Key Features
Loop Mode
The most requested feature – text that types, pauses, deletes, and repeats:
const { displayText, isDeleting } = useTypewriter({
text: 'React Terminal Typewriter',
loop: true,
loopDelay: 3000, // Wait 3s before deleting
deleteDelay: 30 // Delete faster than typing
})
// Change styles based on state
<span style={{ color: isDeleting ? 'orange' : 'green' }}>
{displayText}
</span>Configurable Speeds
Fine-tune every aspect of the animation:
| Option | Default | Description |
|---|---|---|
delay |
100ms | Typing speed |
startDelay |
500ms | Initial delay |
loopDelay |
2000ms | Pause before deleting |
deleteDelay |
50ms | Deletion speed |
cursorBlinkSpeed |
800ms | Cursor blink rate |
Terminal Style Example
Perfect for command-line aesthetics:
const { displayText, cursorBlinkSpeed } = useTypewriter({
text: 'npm install react-terminal-typewriter',
delay: 50,
startDelay: 1000
})
return (
<div className="terminal">
<span className="prompt">$ </span>
{displayText}
<span className="cursor" />
</div>
)Technical Implementation
React 18+ StrictMode Handling
The biggest challenge was handling React 18's StrictMode, which intentionally double-invokes effects in development. My solution uses refs to track running state:
const isRunningRef = useRef(false)
useEffect(() => {
if (isRunningRef.current) return
isRunningRef.current = true
// Animation logic...
return () => {
isRunningRef.current = false
clearTimeout(timeoutRef.current)
}
}, [text])Recursive setTimeout Pattern
Instead of setInterval, I use recursive setTimeout for precise timing control:
const tick = () => {
if (isDeletingRef.current) {
// Delete character
setDisplayText(prev => prev.slice(0, -1))
timeoutRef.current = setTimeout(tick, deleteDelay)
} else {
// Type character
setDisplayText(text.slice(0, indexRef.current + 1))
indexRef.current++
timeoutRef.current = setTimeout(tick, delay)
}
}This approach ensures:
- Consistent timing regardless of React re-renders
- Clean cleanup on unmount
- Proper state synchronization
What I Learned
Building this package taught me:
- npm publishing workflow – from
package.jsonsetup to GitHub Actions CI/CD - Library bundling – using tsup for ESM/CJS dual output
- React internals – deep understanding of useEffect lifecycle
- TypeScript best practices – proper type exports and declarations
- Documentation importance – README, examples, and live demo
Try It Yourself
🔗 Live Demo: vitalii4reva.github.io/react-terminal-typewriter
💻 Source Code: github.com/vitalii4reva/react-terminal-typewriter
📦 npm Package: npmjs.com/package/react-terminal-typewriter
The package is MIT licensed and open for contributions!
What's Next?
Planned features:
- Multiple text strings – cycle through an array of texts
- Pause/Resume control – programmatic animation control
- Custom easing – variable typing speeds for more natural effect
- onComplete callback – trigger actions when typing finishes
Final Thoughts
Sometimes the best solution is the simplest one. By focusing on a single hook with zero dependencies, I created something that's easy to use, maintain, and extend.
Need a typewriter effect in your React app? Give react-terminal-typewriter a try. And if you find bugs or have feature requests, open an issue on GitHub!
What's your favorite UI animation? Do you prefer building your own solutions or using libraries? Let me know!