typingfx
Version:
Customizable, smooth, and snappy typing animations for React — built for natural, human-like effects with minimal config.
529 lines (371 loc) • 14.1 kB
Markdown
# TypingFX <img src="https://raw.githubusercontent.com/mayank1513/mayank1513/main/popper.png" style="height: 40px"/>
[](https://github.com/react18-tools/typingfx/actions/workflows/test.yml)
[](https://codeclimate.com/github/react18-tools/typingfx/maintainability)
[](https://codecov.io/gh/react18-tools/typingfx)
[](https://www.npmjs.com/package/typingfx)
[](https://www.npmjs.com/package/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
```bash
pnpm add typingfx
```
**_or_**
```bash
npm install typingfx
```
**_or_**
```bash
yarn add typingfx
```
## 🔧 Usage
### ① Import Styles
> 🎨 Required for typing animation and cursor styling.
**In JSX (Next.js layout files recommended):**
```tsx
import "typingfx/dist/index.css";
```
**Or in global CSS:**
```css
@import "typingfx/dist/index.css";
```
### ② Basic Typing Animation
```tsx
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`?
```tsx
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 with `String()` or use template literals.
### ④ Animate JSX & Rich Text
```tsx
<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.
```tsx
<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:
```tsx
<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:
```html
<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:
```css
.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](https://github.com/react18-tools/typingfx/issues) or comment on [this discussion](https://github.com/react18-tools/typingfx/discussions/4).
## 💡 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:
```tsx
<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:
```tsx
<>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**.
```tsx
<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 the `key`** to remount
```tsx
<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**:
```ts
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 per `id`. 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 new `key`.
- 💥 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:
```tsx
<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:
```tsx
// ❌ 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?
```tsx
<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
<details>
<summary>⚠️ <strong>Why does the animation restart on every re-render?</strong></summary>
This usually happens when `steps` or `children` are _not memoized_, causing a new reference on every render.
✅ **Fix:** Use `useMemo` to ensure stable references:
```tsx
const steps = useMemo(() => ["Hello", "World"], []);
<TypeOut steps={steps} />;
```
</details>
<details>
<summary>📏 <strong>Why is there extra space when adding delays between elements?</strong></summary>
Inserting a delay like `{500}` **between block elements** (e.g. `<p>`) creates empty text nodes, leading to layout shifts.
❌ Bad:
```tsx
<>
<p>Hi</p>
{500}
<p>Hello</p>
</>
```
✅ Good:
```tsx
<>
<p>Hi{500}</p>
<p>Hello</p>
</>
```
> 📌 **Tip:** Always place delays _inside_ a block to avoid glitches.
</details>
<details>
<summary>🧩 <strong>Does it work with RSC & Next.js 14/15?</strong></summary>
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)
</details>
<details>
<summary>⏪ <strong>How do I add delay during deleting?</strong></summary>
Use **negative numbers** like `{-500}` anywhere in the content.
- `{800}` → pause for 800ms while typing
- `{-500}` → pause for 500ms while deleting
```tsx
<TypeOut steps={["Start typing...", -1000, "Deleting now..."]} />
```
or
```tsx
<TypeOut>Wait before deleting me{-500}</TypeOut>
```
</details>
<details>
<summary>🎨 <strong>How do I animate styled or rich text?</strong></summary>
TypingFX supports JSX out of the box! You can mix `<strong>`, `<code>`, emojis, or even full components.
```tsx
<TypeOut
steps={[
<>
Writing with <strong>style</strong>
</>,
<>
Deployed via <code>Vercel</code>
</>,
<>💻 Happy Coding!</>,
]}
/>
```
> ✨ Fully supports React elements, fragments, and inline styles.
</details>
## 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](https://github.com/mayank1513)
> <img src="https://raw.githubusercontent.com/mayank1513/mayank1513/main/popper.png" style="height: 20px"/> Please enroll in [our courses](https://mayank-chaudhari.vercel.app/courses) or [sponsor](https://github.com/sponsors/mayank1513) our work.
<hr />
<p align="center" style="text-align:center">with 💖 by <a href="https://mayank-chaudhari.vercel.app" target="_blank">Mayank Kumar Chaudhari</a></p>