TypingFX 
⚡ Customizable, smooth, and snappy typing animations for React
✨ Animate your text like a pro — fully compatible with React 18/19, Next.js 14/15, and React Server Components.
✨ Features
- 🎯 Built for modern React (18/19) and Next.js (14/15)
- ✨ Smooth, realistic word-by-word animation
- 🔁 Step-based sequences with infinite looping
- 💅 JSX-ready — animate styled, rich text effortlessly
- 🧠 Honors
prefers-reduced-motion
accessibility setting - 🚀 Hybrid CSS + JS for best performance
- ⚡ Smarter performance: animation pauses 💤 when the tab loses focus — saving CPU and improving battery life without breaking the flow.
- 🎨 Fully customizable cursor with auto-blink
- 🔁 Infinite/controlled looping
- 💡 Fully typed with TypeScript
- 🧩 SSR-safe and RSC-compatible
🚀 Install
pnpm add typingfx
or
npm install typingfx
or
yarn add typingfx
🔧 Usage
① Import Styles
🎨 Required for typing animation and cursor styling.
In JSX (Next.js layout files recommended):
import "typingfx/dist/index.css";
Or in global CSS:
@import "typingfx/dist/index.css";
② Basic Typing Animation
import { TypeOut } from "typingfx";
export default function Example() {
return (
<TypeOut
steps={["Frontend Developer.", "React Enthusiast.", "Open Source Advocate."]}
speed={25}
delSpeed={40}
repeat={Infinity}
/>
);
}
③ Single-Step Typing
Need a one-off typing effect without using steps
?
export default function Example() {
return (
<TypeOut>
I love {500} <strong>typingfx</strong>
</TypeOut>
);
}
⏱️ Use numbers inside JSX to insert pauses (in milliseconds) during typing.
➖ Negative numbers delay deletion.
🔤 Want to render the number instead? Wrap it withString()
or use template literals.
④ Animate JSX & Rich Text
<TypeOut
steps={[
<>
Building with <strong>React</strong>
</>,
<>
Deploying on <code>Vercel</code>
</>,
<>
Coding like a <span className="emoji">💻</span>
</>,
]}
speed={30}
repeat={3}
/>
🧪 Component Animation (Beta)
TypingFX supports animating React components using a typing and deleting effect. This feature is currently in beta, and feedback is welcome.
✨ Default Behavior – Typing Animation
By default, TypingFX assumes the component is pure and attempts to extract and animate its JSX output, treating it like static content. This provides a natural typing effect as if the component was written inline.
<TypingFX>{<MyPureComponent />}</TypingFX>
This enables smooth, character-by-character typing that matches the surrounding text.
🧩 componentAnimation
Prop
For components with side effects or dynamic behavior, you can control their animation using the componentAnimation
prop:
<TypingFX
componentAnimation={{
wrapper: "div",
props: { className: "custom-wrapper" },
}}>
<MyComponent />
</TypingFX>
TypingFX will wrap the component with the specified element and apply:
- Fade-in (typing): 5s
- Fade-out (deleting): 3s
This disables JSX extraction and uses a wrapper-based animation strategy.
⚠️ Server-Side Rendering (SSR) Limitation
TypingFX cannot detect components in SSR environments. Thus, by default, SSR-rendered components are treated as normal content and animated using the default typing animation.
However, you can manually mark any DOM element to be treated as a component by adding a data-tfx
attribute with any truthy value:
<span data-tfx="true">Server-rendered content</span>
Combined with the componentAnimation
prop, this enables custom animation support even for SSR-rendered output.
🎨 CSS Overrides
You can override the fade animation by targeting the default class names:
.tfx_component.tfx_type {
animation: myCustomFadeIn 2s ease-in;
}
.tfx_component.tfx_del {
animation: myCustomFadeOut 2s ease-out;
}
💬 API Feedback Welcome
We're exploring the best API design for component animation. If you have ideas or requirements, please open an issue or comment on this discussion.
💡 Tips & Tricks
⏱️ Delays & Pauses with Numbers
You can embed numbers (e.g. {1000}
) directly inside JSX (steps
or children
) to add typing or deleting delays:
<TypeOut
steps={[
<>
Hello{800}
{-500}
</>,
"World!",
]}
/>
{800}
→ pauses for 800ms while typing{-500}
→ pauses for 500ms while deleting
⚠️ Important: Numbers must be embedded directly as JSX expressions — not as strings.
If you want to display a number instead of pausing, convert it to a string:
<>I waited {String(800)} milliseconds.</>
⚠️ Memoization Matters
To prevent unintended animation restarts on re-renders, memoize your steps
or children
using useMemo
:
const steps = useMemo(() => ["One", "Two", "Three"], []);
TypeOut
is memoized by default to prevent unwanted animation restarts and infinite loops.
<TypeOut steps={steps} />
🔁 It ignores all prop changes after first render, including
paused
,speed
, etc.
- 🧊 Props are frozen on mount
- 🧠 Re-renders won't restart animation
- ✅ To re-run animation with new
steps
, change thekey
to remount
<TypeOut key={id} steps={steps} />
🧩 Dynamic Prop Updates (Without Re-renders or Infinite Loops)
Want to update props on the fly (e.g., pause/resume, adjust speed) without triggering full React re-renders or loop traps?
Use the useUpdate()
hook with double parentheses:
import { useUpdate } from "typeout";
export function Demo() {
const [paused, setPaused] = useUpdate("demo")(state => [state.paused, state.setPaused]);
// if you want to update props globally do not pass any id -> useUpdate()(state => [...])
return (
<div className={styles.demo}>
<button onClick={() => setPaused(!paused)}>{paused ? "Resume" : "Pause"}</button>
<TypeOut steps={steps} storeId="demo" />
</div>
);
}
🎯
useUpdate()
returns an isolated reactive store perid
. These updates:
- Take effect immediately
- Don’t rely on props or re-renders
- Work perfectly with interactive controls
🔥 Important Caveats
- ⚠️
steps
cannot be updated dynamically — changing them requires a remount via a newkey
. - 💥 remounting via key will kill the current animation and start new one.
- ✅ All other animation props are supported via
useUpdate()
.
✅ Supported Setters
Setter | Description |
---|---|
setSpeed(speed) |
Typing speed (chars/sec) |
setDelSpeed(delSpeed) |
Deletion speed (chars/sec) |
setNoCursor(boolean) |
Toggle cursor visibility |
setNoCursorAfterAnimEnd |
Hide cursor after animation ends |
setRepeat(count) |
How many times to repeat the animation |
setForce(boolean) |
Override user’s reduced motion setting |
setPaused(boolean) |
Pause or resume |
setComponentAnimation() |
Update animation for custom components |
ℹ️ These updates are instance-local, fast, and non-intrusive. Use the
storeId
prop to group/manage the animation props across TypeOut instances.
🧱 Multi-Line Typing Support
Each step can contain multiple elements like <p>
, <div>
, or fragments – typingfx
will animate them line by line:
<TypeOut
steps={[
<>
<p>Hi there</p>
<p>Welcome to TypingFX!</p>
</>,
<>
<p>Hi there</p>
<p>TypingFX is awesome!</p>
</>,
]}
/>
✅ No need to split them into separate steps – group them as one step to animate fluidly across lines.
🚫 Avoid Layout Shifts on Delays
When inserting delays between block elements, prefer placing the delay inside one block, rather than between them:
// ❌ Avoid: causes extra spacing
<>
<p>Hi</p>
{5000}
<p>Hello</p>
</>
// ✅ Recommended
<>
<p>Hi{5000}</p>
<p>Hello</p>
</>
// or
<>
<p>Hi</p>
<p>{5000}Hello</p>
</>
✨ Control the Cursor
Want to hide the blinking cursor?
<TypeOut noCursor>Hello, no cursor here!</TypeOut>
🌐 Seamless RSC & Next.js Support
No extra setup needed – TypeOut
is already marked as a client component.
✅ Works out of the box with Next.js 14/15 and React Server Components (RSC).
⚙️ Props
Prop | Type | Default | Description |
---|---|---|---|
steps |
ReactNode[] |
[""] |
The sequence of text or elements to animate. |
speed |
number |
20 |
Typing speed (characters per second). |
delSpeed |
number |
40 |
Deletion speed (characters per second). |
repeat |
number |
Infinity |
Number of times to loop over steps. |
noCursor |
boolean |
false |
Disables the blinking cursor. |
paused |
boolean |
false |
Manually pause or resume animation. |
force |
boolean |
false |
Forces animation even when prefers-reduced-motion is on. |
children |
ReactNode |
"" |
An optional initial step to animate. |
... | HTMLDivProps |
— | Additional props are passed to the container element. |
📦 Framework Compatibility
- ✅ React 16.8 — React 19
- ✅ Next.js 12 — Next.js 15
- ✅ SSR-safe (no
window
access during render) - ✅ RSC-compatible (used as a client component)
❓ FAQ & Gotchas
This usually happens when steps
or children
are not memoized, causing a new reference on every render.
✅ Fix: Use useMemo
to ensure stable references:
const steps = useMemo(() => ["Hello", "World"], []);
<TypeOut steps={steps} />;
Inserting a delay like {500}
between block elements (e.g. <p>
) creates empty text nodes, leading to layout shifts.
❌ Bad:
<>
<p>Hi</p>
{500}
<p>Hello</p>
</>
✅ Good:
<>
<p>Hi{500}</p>
<p>Hello</p>
</>
📌 Tip: Always place delays inside a block to avoid glitches.
Absolutely. TypeOut
is already marked as a client component, so it works out of the box with:
- ✅ React Server Components (RSC)
- ✅ App Router
- ✅ Server-side Rendering (SSR)
Use negative numbers like {-500}
anywhere in the content.
{800}
→ pause for 800ms while typing{-500}
→ pause for 500ms while deleting
<TypeOut steps={["Start typing...", -1000, "Deleting now..."]} />
or
<TypeOut>Wait before deleting me{-500}</TypeOut>
TypingFX supports JSX out of the box! You can mix <strong>
, <code>
, emojis, or even full components.
<TypeOut
steps={[
<>
Writing with <strong>style</strong>
</>,
<>
Deployed via <code>Vercel</code>
</>,
<>💻 Happy Coding!</>,
]}
/>
✨ Fully supports React elements, fragments, and inline styles.
Updates
Removed next
from peerDependencies 🧼
next
was previously listed in optionalPeerDependencies
only to indicate compatibility. It has been removed:
- ✅ TypingFX still works perfectly in Next.js (including App Router, SSR, RSC)
- 🧼 Avoids triggering npm warnings in non-Next projects
- 📚 Compatibility now noted in docs + keywords only
📁 License
MPL-2.0 open-source license © Mayank Chaudhari
Please enroll in our courses or sponsor our work.
with 💖 by Mayank Kumar Chaudhari