ComponentsuseEffect + setInterval. Tag personalizzabile (h1/h2/h3/p/span). Caret 1px su animation steps(2).
TextTypewriterClient
Esempio01
typing-animation.tsx 001Default · phrase002Hero · large headline003Code · mono command tsxsrc/components/typing-animation.tsx
"use client";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
export type TypingAnimationProps = {
text: string;
/** Milliseconds per character. Default 50. */
duration?: number;
/** Delay before typing starts in ms. Default 0. */
delay?: number;
/** Wrapper element. Default `"p"`. */
as?: "h1" | "h2" | "h3" | "p" | "span";
className?: string;
};
/**
* <TypingAnimation/> — typewriter that reveals `text` character-by-character
* with a blinking caret. Caret stops blinking once the string is complete.
*/
export function TypingAnimation({
text,
duration = 50,
delay = 0,
as: Tag = "p",
className,
}: TypingAnimationProps) {
const [shown, setShown] = useState("");
const [done, setDone] = useState(false);
useEffect(() => {
setShown("");
setDone(false);
let i = 0;
let intervalId: ReturnType<typeof setInterval> | undefined;
const startId = setTimeout(() => {
intervalId = setInterval(() => {
i += 1;
setShown(text.slice(0, i));
if (i >= text.length) {
setDone(true);
if (intervalId) clearInterval(intervalId);
}
}, Math.max(8, duration));
}, delay);
return () => {
clearTimeout(startId);
if (intervalId) clearInterval(intervalId);
};
}, [text, duration, delay]);
return (
<>
<style>{`
@keyframes typing-caret-blink { 50% { opacity: 0; } }
`}</style>
<Tag className={cn("inline-flex items-baseline", className)}>
<span>{shown}</span>
<span
aria-hidden
className="ml-[2px] inline-block w-[1px] self-stretch bg-current"
style={{
animation: done
? undefined
: "typing-caret-blink 1s steps(2) infinite",
}}
/>
</Tag>
</>
);
}
Note — Cleanup completo su unmount (clearTimeout + clearInterval). Il blinking si ferma quando shown.length === text.length.
Prompt LLM02
Incolla in Claude o ChatGPT per generare la tua variante. Include il contesto del brand, i token e i vincoli del progetto.
Prompt · typing-animation Sei un senior frontend engineer. Stai lavorando su un sito Next.js 16 + React 19 + Tailwind v4 in italiano, look chanhdai-inspired: colonna stretta 672px, Geist Sans + Geist Mono, hairline 1px, divisori a stripe diagonale, palette zinc.
Token CSS disponibili: --bg, --bg-alt, --fg, --fg-muted, --fg-soft, --border, --border-strong, --accent. Usa SEMPRE queste variabili tramite le utility tailwind generate (bg-bg, text-fg-muted, border-border, ecc.). Helper "cn" da "@/lib/utils". Niente librerie UI extra: solo lucide-react e tailwind-merge.
Genera un componente <TypingAnimation> typewriter.
Props:
- text: string.
- duration?: number (ms per char, default 50).
- delay?: number (ms prima di iniziare, default 0).
- as?: "h1"|"h2"|"h3"|"p"|"span" (default "p").
- className?: string.
Implementazione:
- "use client". useState shown + done. useEffect: setTimeout(delay) → setInterval(duration) che incrementa l'indice e fa setShown(text.slice(0, i)).
- Caret span animato con keyframe typing-caret-blink (50% opacity 0). Disabilita animation quando done.
- Cleanup: clearTimeout + clearInterval.
Output: file completo src/components/typing-animation.tsx.
Uso tipico03
<TypingAnimation text="Hello, world." duration={50} />
Dipendenze04
Ti è servito? Dimmelo, oppure proponi il prossimo componente.