Kasumi
A typewriter library for React. Characters materialize through haze.
Install / Use
/learn @tigerabrodi/KasumiREADME
kasumi 霞
A typewriter library for React. Characters materialize through haze. Feels natural. Looks premium. Zero CSS required.
Install
npm install @tigerabrodioss/kasumi
Quick start
import { Typewriter } from '@tigerabrodioss/kasumi'
;<Typewriter text="What emails can I help you" />
That is all you need. It types, it blurs, it decelerates at the end.
Loop through multiple strings
<Typewriter text={['Design faster.', 'Ship sooner.', 'Sleep better.']} loop />
The hook. For full control.
When you need custom markup. Two lines on mobile, one on desktop. An animation fading in at the end. Anything the component cannot express.
import { useTypewriter } from '@tigerabrodioss/kasumi'
const { segments, isDone, isTyping } = useTypewriter({
text: 'What emails can I help you',
})
return (
<h1>
{segments.map((seg) => (
<span key={seg.index} ref={seg.ref}>
{seg.char}
</span>
))}
</h1>
)
seg.ref is what wires the blur animation via the Web Animations API. You attach it to whatever element you want. You own the markup entirely.
Segments
Each segment has this shape.
type Segment = {
char: string
state: 'stable' | 'blurring' | 'hidden'
ref: (el: HTMLElement | null) => void
index: number
}
stable — fully visible. no animation.
blurring — currently animating in via WAAPI.
hidden — not visible yet. rendered invisibly so layout does not shift.
Feel presets
<Typewriter text="Hello" feel="cinematic" /> // default. slow start, decelerates at end
<Typewriter text="Hello" feel="snappy" /> // faster, tighter curve
<Typewriter text="Hello" feel="playful" /> // slight randomness per character
Or bring your own curve. The function receives progress (0 to 1) and returns the delay in ms for that character.
<Typewriter
text="Hello"
feel={{
curve: ({ progress, index, total }) => {
return 80 - progress * 60
},
}}
/>
Blur config
<Typewriter
text="Hello"
blur={{
trailLength: 4, // how many chars blur in at once. default 4
duration: 300, // ms per char animation. default 300
amount: 8, // blur intensity in px. default 8
easing: 'ease-out', // css easing string. default "ease-out"
}}
/>
Disable blur entirely.
<Typewriter text="Hello" blur={false} />
Render as any element
<Typewriter text="Hello" as="h1" className="text-4xl font-medium" />
<Typewriter text="Hello" as="p" className="text-base" />
Default is span.
Timing
<Typewriter
text="Hello"
initialDelay={100} // ms before typing starts. default 0
pauseAfter={1200} // ms to wait at end before deleting. loop only. default 1200
/>
Callbacks
<Typewriter
text="Hello"
onStart={() => {}}
onDone={() => {}}
onCharTyped={({ char, index }) => {}}
onDelete={({ char, index }) => {}} // loop only
/>
useTypewriter full reference
const { segments, isDone, isTyping, isDeleting, restart, pause, resume } =
useTypewriter({
text: 'Hello', // string or string[]
feel: 'cinematic', // preset or { curve: fn }
blur: {
trailLength: 4,
duration: 300,
amount: 8,
easing: 'ease-out',
},
loop: false,
initialDelay: 0,
pauseAfter: 1200,
onStart: () => {},
onDone: () => {},
onCharTyped: ({ char, index }) => {},
onDelete: ({ char, index }) => {},
})
Result
| Field | Type | Description |
| ------------ | ------------ | ------------------------------------------------------------ |
| segments | Segment[] | Current characters with their state and blur ref |
| isDone | boolean | True when all characters are typed and animation is complete |
| isTyping | boolean | True while characters are actively being typed |
| isDeleting | boolean | True while characters are being deleted (loop mode) |
| restart | () => void | Reset and replay the animation from the beginning |
| pause | () => void | Pause mid-animation |
| resume | () => void | Resume from where it paused |
Full TypeScript types
type Segment = {
char: string
state: 'stable' | 'blurring' | 'hidden'
ref: (el: HTMLElement | null) => void
index: number
}
type BlurOptions = {
trailLength?: number
duration?: number
amount?: number
easing?: string
}
type FeelCurve = (params: {
progress: number
index: number
total: number
}) => number
type FeelConfig = 'cinematic' | 'snappy' | 'playful' | { curve: FeelCurve }
type UseTypewriterOptions = {
text: string | string[]
feel?: FeelConfig
blur?: BlurOptions | false
loop?: boolean
initialDelay?: number
pauseAfter?: number
onStart?: () => void
onDone?: () => void
onCharTyped?: (params: { char: string; index: number }) => void
onDelete?: (params: { char: string; index: number }) => void
}
type UseTypewriterResult = {
segments: Segment[]
isDone: boolean
isTyping: boolean
isDeleting: boolean
restart: () => void
pause: () => void
resume: () => void
}
type TypewriterProps = UseTypewriterOptions & {
as?: keyof HTMLElementTagNameMap
className?: string
}
Why kasumi
霞 means haze or mist in Japanese. Characters appear as if materializing from mist. That is the feeling this library is built around.
Related Skills
node-connect
348.2kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
108.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
348.2kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
348.2kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
