Typing Text
The TypingText component creates a typing effect for text content. It's perfect for creating dynamic text entrances that simulate typing on a keyboard.
Usage
Welcome to Our Platform
<template>
<div class="flex flex-col items-center space-y-6 p-6">
<div class="text-center">
<h2 class="text-2xl font-bold text-highlighted mb-2">
Welcome to Our Platform
</h2>
<MTypingText
text="Discover amazing features and boost your productivity"
class="text-lg text-muted"
/>
</div>
<div class="bg-default rounded-lg shadow-md p-4 max-w-md w-full">
<div class="flex items-center space-x-3 mb-3">
<div class="w-3 h-3 bg-red-500 rounded-full" />
<div class="w-3 h-3 bg-yellow-500 rounded-full" />
<div class="w-3 h-3 bg-green-500 rounded-full" />
</div>
<div class="space-y-2">
<div class="h-4 bg-accented rounded w-3/4" />
<div class="h-4 bg-accented rounded w-full" />
<div class="h-4 bg-accented rounded w-5/6" />
</div>
</div>
</div>
</template>
Examples
Cycling through phrases
<template>
<div class="p-4">
<MTypingText
:texts="['First Text', 'Second Text', 'Third Text']"
:loop="true"
class="text-xl font-bold text-[#0d9488]"
/>
</div>
</template>
With texts filled, loop keeps the component cycling: each phrase types out,
holds for pauseDuration, clears, and the next one starts. Without loop, the
component types the first entry and stops.
Command-line cursor
Typing a deploy command at 70ms per character…
<template>
<div class="flex flex-col items-center gap-4 p-8">
<MTypingText
text="npm run deploy --production"
class="font-mono text-lg text-default"
:speed="70"
cursor="▊"
cursor-class="text-primary"
@complete="completed = true"
/>
<p class="text-sm text-muted">
{{ completed ? 'Deploy finished — the complete event fired.' : 'Typing a deploy command at 70ms per character…' }}
</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const completed = ref(false)
</script>
cursor swaps the character and cursorClass tints it, while the complete
event fires each time the string finishes — the same point at which
onComplete is called.
Blur-in headline
The `blurInUp` preset arrives from below through a 10px blur, starting 400ms after mount. With the cursor hidden the line reads as a headline rather than as input.
<template>
<div class="flex flex-col items-center gap-4 p-8 text-center">
<h3 class="text-2xl font-bold text-highlighted">
<MTypingText
text="Meet the new release"
animation="blurInUp"
:delay="400"
:speed="80"
:show-cursor="false"
/>
</h3>
<p class="max-w-sm text-sm text-muted">
The `blurInUp` preset arrives from below through a 10px blur, starting
400ms after mount. With the cursor hidden the line reads as a headline
rather than as input.
</p>
</div>
</template>
The animation preset chooses the container's entrance — opacity, and
optionally blur, y/x or scale — while the typing itself is independent
of it. Hiding the cursor with showCursor is what separates a headline from a
command line.
Customize
Customize
Props
| Prop | Type | Default | Description |
|---|---|---|---|
text | string | '' | Text to type. |
texts | string[] | undefined | Array of texts to cycle through. |
speed | number | 100 | Typing speed in milliseconds. |
delay | number | 0 | Delay before starting animation. |
showCursor | boolean | true | Whether to show cursor. |
cursor | string | '|' | Cursor character. |
cursorClass | string | '' | CSS classes for cursor. |
loop | boolean | false | Whether to loop through texts. Requires more than one entry in texts. |
pauseDuration | number | 2000 | Pause duration between loops. |
class | string | '' | CSS classes to apply to the container. |
onComplete | function | undefined | Callback when typing completes. Fires at the same point as the complete
event. |
startOnView | boolean | true | Start animation when component enters viewport. |
once | boolean | false | Animate only once. |
animation | string | 'fadeIn' | Animation preset to use: fadeIn, blurIn, blurInUp, blurInDown,
slideUp, slideDown, slideLeft, slideRight, scaleUp or scaleDown. |
inViewMargin | string | '0px' | Margin for in-view detection. |
ui | object | undefined | Per-slot classes merged over the theme defaults. See UI. |
Events
| Event | Payload | Description |
|---|---|---|
complete | () => void | Emitted each time the current string finishes typing — once per phrase when
cycling, and before every pauseDuration gap in a loop. |
UI
This component's theme exposes the slots below. Override any of them per instance with the ui prop, or app-wide under ui.typingText in app.config.ts.
| Prop | Type | Default | Description |
|---|---|---|---|
ui | object | - | Per-slot classes merged over the theme defaults. |
| Slot | Props | Description |
|---|---|---|
root | - | typing-text-container whitespace-pre-wrap |
text | - | inline-flex items-center |
cursor | - | typing-cursor inline-block ms-1 font-normal select-none w-px |
Accessibility
The typed text lives in a plain span inside the motion container. There is no
aria-live region, so assistive technology reads whatever characters are in the
DOM when it queries — mid-type that is a prefix, and after typing it is the full
string. For a heading whose finished content matters, keep the final string
available as real text somewhere on the page rather than relying on the typing
animation to have finished.
startOnView (on by default) only defers when typing begins, using
useElementVisibility with inViewMargin — it changes nothing about what is
announced. The animation preset likewise affects only the container's entrance.
There is no reduced-motion handling: the typing cadence, the per-character reveal
and the cursor blink all run the same under prefers-reduced-motion.
Performance
- Typing is a
setTimeoutchain — one scheduled callback at a time atspeedmilliseconds, each tick writing one longer string into a text node. It is timer work, not frame work; between ticks the component does nothing. - The container entrance animates
opacityand, depending on the preset,filterorx/y/scaleover a fixed0.6s, all compositor-friendly except the blur presets. The cursor is one moreMotionspan running a single infinite opacity keyframe — the only continuous animation. - Node count is constant — container, text span and cursor — no matter how long the string gets; the growing text is one text node.
startOnViewkeepsuseElementVisibilitysubscribed until unmount;onceis enforced by a state flag rather than by disconnecting the observer.- All pending timeouts are cleared on unmount, and loop restarts reuse the same slot rather than stacking timers.