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-motionaccessibility 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 typingfxor
npm install typingfxor
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 thekeyto 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
- ⚠️
stepscannot 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
storeIdprop 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
windowaccess 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