Typewriter <mv-typewriter>
ヒーローの見出し向けのタイプライター:不規則なリズムと句読点での間を持たせて 1 文字ずつフレーズを入力し、バー、ブロック、アンダースコアのキャレットの後ろで消去と切り替えを繰り返します。最終サイズで確保されたボックスの中で動くので、周囲は一切動きません。
| カテゴリー | アニメーションテキスト |
|---|---|
| タイプ | Web Component(<mv-typewriter>) |
| ステータス | 安定版 |
| Keywords | typewriter, typing, caret, cursor, rotating, hero, terminal |
When to use
- A hero line should type several phrases in a loop, deleting between them, without pushing the layout around
- A terminal or conversational tone needs realistic irregular typing with pauses on punctuation
- Animated copy must stay readable by screen readers and findable with the browser search
Avoid when
- A single word should change inside a sentence with a smooth width glide → use Word Rotate instead
- Key information only appears after long typing delays; visitors may leave before reading it
インストール
node scripts/add.mjs typewriter --out ./src/marvelousMarvelous UI の MCP サーバーを使う AI エージェント:install_components({ slugs: ["typewriter"], target_dir: "<absolute path>/src/marvelous", framework: "react" })。
コピーされるファイル(依存関係を含む):tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, core/observe.js, components/typewriter/typewriter.js, components/typewriter/typewriter.css。
使い方
基本となるマークアップです。ここから属性、data-*、CSS 変数でカスタマイズしてください:
<div id="tw-demo" style="display:grid;gap:1.75rem;width:min(100%,44rem);margin-inline:auto;text-align:center">
<h2 style="margin:0;font:700 clamp(1.8rem,4.4vw,3rem)/1.1 var(--mv-font-display);letter-spacing:-.035em">
We build<br>
<mv-typewriter loop words="sites that convert., apps people keep., brands that stick." colors="var(--mv-accent), var(--mv-info), var(--mv-success)"></mv-typewriter>
</h2>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(18rem,100%),1fr));gap:.75rem;text-align:start">
<div style="padding:1rem 1.1rem;border-radius:var(--mv-radius-lg);background:var(--mv-gray-950);border:1px solid var(--mv-border);color:var(--mv-gray-100);font:500 .9rem/1.6 var(--mv-font-mono);box-shadow:var(--mv-shadow-md)">
<span style="color:var(--mv-gray-400)">~/project $</span>
<mv-typewriter caret="block" speed="45" delay="600" style="--mv-typewriter-caret:var(--mv-gray-300)">npx mv add typewriter</mv-typewriter>
</div>
<div style="padding:1rem 1.1rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-lg);background:var(--mv-surface);font-size:1.05rem">
<mv-typewriter caret="underscore" loop pause="1400">
<span>Hi, I’m Iris.</span>
<span>I write your briefs.</span>
<span>I summarize your meetings.</span>
</mv-typewriter>
</div>
</div>
<div style="display:flex;justify-content:center">
<button class="mv-button" data-variant="outline" data-size="sm" id="tw-demo-replay">Replay</button>
</div>
</div>
<script type="module">
const root = document.getElementById("tw-demo");
root.querySelector("#tw-demo-replay").addEventListener("click", () => {
root.querySelectorAll("mv-typewriter").forEach((el) => el.replay());
});
</script>API
Attributes
| Name | タイプ | Default | Description |
|---|---|---|---|
words | string | Phrases separated by commas (or by | if they contain commas). Otherwise: <span> children (their classes stay on the phrase) or plain text. | |
loop | boolean | Deletes and cycles through the phrases in a loop. Without loop, stops on the last phrase. Hover or focus holds the current phrase. | |
caret | bar | block | underscore | none | bar | Caret style: solid while typing, a few blinks at rest, then steady. |
speed | number (ms) | 55 | Average delay between two letters. |
delete-speed | number (ms) | 28 | Delay between two deletions. |
pause | number (ms) | 1800 | Reading time before deleting. |
delay | number (ms) | 0 | Delay before the first letter. |
jitter | number 0-1 | 0.45 | Typing irregularity (0 = metronome). |
colors | list of CSS colors | Color per phrase, cycling (tokens accepted). | |
trigger | view | load | hover | manual | view | Trigger (combinable). view starts on first view, hover replays a finished sequence, manual shows the final text and waits for replay(). |
Methods
| Name | Description |
|---|---|
replay() | Restarts from the first phrase. |
Events
| Name | Description |
|---|---|
mv-change | New phrase started (detail.index). |
mv-end | Last phrase typed (without loop). |
CSS classes
| Name | Description |
|---|---|
mv-typewriter-phrase | One per phrase (generated, aria-hidden). The one on screen holds -typed, -caret and -pending; the others are hidden="until-found". |
mv-typewriter-typed / -pending | Typed part and untyped rest of the current phrase; the rest keeps its place but is not painted. |
mv-typewriter-caret | Zero-width caret anchor; the caret is drawn by its ::before. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-typewriter-caret | var(--mv-accent) | Caret color. |
--mv-typewriter-blink | 1.1s | Blink period. |
Accessibility
Every phrase stays real, complete text: typing only hides the untyped rest of the visible phrase, so the box is reserved at its final size and never shifts. Screen readers read all phrases once, in order, from a visually hidden copy; the animated layer is aria-hidden and has no aria-live. Find in page matches the visible phrase and the hidden ones (hidden="until-found"): a match shows that phrase whole and stops the cycle. Hovering or focusing it (or the link around it) holds the current phrase and types back one being erased. The sequence freezes off screen and in a hidden tab. Reduced motion: the text appears at once, with a steady caret. The caret blinks a few times, then stays lit. Forced colors: the caret uses CanvasText. Print shows the whole phrase.