View this page with live demos!
The typewritten-text
element represents text that should be typed out one letter at a time when displayed.
This
This <typewritten-text paused>typewriter effect</typewritten-text> is achieved
using <typewritten-text paused>custom elements!</typewritten-text>
Installation
You can import through CDN:
<link rel="stylesheet" href="https://unpkg.com/@auroratide/typewritten-text/lib/style.css" />
<script type="module" src="https://unpkg.com/@auroratide/typewritten-text/lib/define.js"></script>
Or, you may install through NPM and include it as part of your build process:
$ npm i @auroratide/typewritten-text
import '@auroratide/typewritten-text/lib/style.css'
import '@auroratide/typewritten-text/lib/define.js'
Note: How you import into your project depends on its configuration. The style.css
file should be imported with your root CSS, and the define.js
file should imported with your root Javascript.
Usage
typewritten-text
is an inline markup element that you can use in your HTML document.
Some <typewritten-text>text to type out!</typewritten-text>
Since this is Just HTMLTM, you can use typewritten-text
with other markup tags:
This works with
This works with <typewritten-text paused>
<strong>other</strong>
<em>markdown</em>
<span class="special">elements</span>
</typewritten-text> as well!
Note: typewritten-text
has text-level semantics, meaning it can contain anything that a span
can contain. See Phrasing Content.
Adjust Timing
Use type-speed
or erase-speed
to adjust timing. The time provided is number of milliseconds between each letter.
Slow to type, fast to erase Fast to type, slow to erase
<ul>
<li><typewritten-text type-speed="200" erase-speed="30">Slow to type, fast to erase</typewritten-text></li>
<li><typewritten-text type-speed="30" erase-speed="200">Fast to type, slow to erase</typewritten-text></li>
</ul>
Start Paused
When paused
is specified, it will start paused until invoked by javascript.
<p>Some <typewritten-text paused>text to type out!</typewritten-text></p>
Repeat
Use repeat
to automatically type and erase the phrase. The repeat-interval
attribute can be used to adjust how long between typing and erasing.
<p><typewritten-text repeat repeat-interval="1000">It just keeps typing.</typewritten-text></p>
All Attributes
Attribute | Default | Description |
---|---|---|
paused |
- | Whether the animation should start paused |
type-speed |
80 | Time between each letter in milliseconds |
erase-speed |
50 | Time between completion and restart during a repeat loop in milliseconds |
repeat |
- | Whether the animation should repeat itself after it types or erases |
repeat-interval |
1000 | Amount of time between typing and erasing when in repeat mode |
Style API
Since typewritten-text
is Just HTMLTM, you can style it the same way you style any HTML tag.
typewritten-text {
color: red;
}
Note: Depending on what you want to do, you may run into some Implementation Gotchas.
Cursor
The blinking cursor can be customized with either CSS variables or directly via selectors.
Variable | Default | Description |
---|---|---|
--cursor-width |
0.125em | How wide the cursor is |
--cursor-style |
solid | Whether the cursor is solid, dashed, dotted, etc; can be any border-style value |
--cursor-color |
currentColor | Color of the cursor |
--cursor-interval |
0.7s | The duration of the blink animation |
The cursor can be arbitrarily customized with the following CSS selector:
typewritten-text .cursor.current::after { }
Example Fancy Cursor
Here's a
typewritten-text .cursor.current::after {
border-inline-end: none;
border-block-end: 0.125em solid red;
width: 1ch;
inset-inline-end: -1ch;
}
Javascript API
The element exposes some useful methods to enable custom animation. Once you have obtained a reference to a TypewrittenTextElement
element:
const elem = document.querySelector('typewritten-text')
You can use the following methods:
Method | Description |
---|---|
type() |
Start typing characters until the end |
typeOne() |
Type one character |
erase() |
Start erasing characters until the beginning |
eraseOne() |
Erase one character |
pause() |
Pause the animation cycle if it is currently running |
resume() |
If paused while typing, continue typing and vice versa; if paused as a result of reaching the end of either typing or erasing, will perform the opposite action. |
switchDirection() |
Switch from typing to erasing or vice versa; can be done in the middle of typing or erasing |
reset() |
Completely resets the element and animation; may be useful if the content within the element is dynamic |
Properties
Each attribute can be accessed as a Javascript property.
elem.paused
elem.typeSpeed
elem.eraseSpeed
Other readonly attributes are provided:
elem.length
: The total number of typeable characterselem.position
: The numerical position of the character to type next
Events
The typewritten-text
element dispatches the following events:
Name | When Triggered |
---|---|
type |
Anytime a character is typed into view |
typed |
When the full phrase becomes fully typed |
typing |
When it starts typing after having been paused |
erase |
Anytime a character is removed from view |
erased |
When the full phrase becomes erased |
erasing |
When it starts erasing after having been paused |
resume |
When the animation is started |
paused |
When the animation is paused |
Element Class
The element interface can be accessed in javascript as well, perhaps to be created manually or for typescript type notation.
import { TypewrittenTextElement } from '@auroratide/typewritten-text'
Accessibility
This custom element is built with accessibility in mind!
- The
typewritten-text
element always represents its textual content regardless of visibility state. Screenreaders should read the text in its entirety. - The textual content can be copied and pasted regardless of visibility state.
- The blinking cursor animation is disabled for people who prefer reduced motion
Implementation Gotchas
It is possible the non-trivial implementation of typewritten-text
can lead to unexpected complications with advanced customization.
typewritten-text
works by cloning its inner content into a mirror
slot, within which each letter is wrapped with a span
. The following is an example before-and-after of what the resulting markup looks like once the element has finished rendering:
<typewritten-text>Hey</typewritten-text>
<!-- ...becomes... -->
<typewritten-text>Hey<span slot="mirror">
<span class="cursor current">
<span class="word">
<span class="char">H</span>
<span class="char">e</span>
<span class="char">y</span>
</span>
</span></typewritten-text>
As a result, a selector like typewritten-text > span
will have unexpected results.
This architecture has the following explicit goals:
- Preserve, as much as possible, the way the web developer has specified the usage of the element. This means not overriding the default slot of
typewritten-text
. - Allow the use of semantic markup within
typewritten-text
so it acts as much as possible like a native text-level element. - Prevent layout shift as a result of characters coming into view; the entire content will exist, but will be invisible until typed.
- Allow CSS customizations of the inner markup to apply. This would not be true if the content was cloned into the element's shadow dom.
Showcases
Using the Javascript and Styling interfaces allows you to do all sorts of things.
Typewriter Cycle
You can cycle between different phrases by attaching listeners to the typed
and erased
events.
View the Typewriter Cycle demo on Codepen!
Have you tried our
fresh salads? 🥗 hearty burgers? 🍔 delicious pies? 🥧
<div class="sentence">
<p>Have you tried our</p>
<ul class="typewriter-cycle">
<li><typewritten-text class="active">fresh salads? 🥗</typewritten-text></li>
<li><typewritten-text paused>hearty burgers? 🍔</typewritten-text></li>
<li><typewritten-text paused>delicious pies? 🥧</typewritten-text></li>
</ul>
</div>
.sentence p { display: inline; }
.typewriter-cycle {
display: inline-block;
position: relative;
width: 20ch;
list-style: none;
padding: 0;
margin: 0;
}
.typewriter-cycle li:not(:first-child) {
position: absolute;
inset: 0;
}
typewritten-text { font-weight: bold; }
typewritten-text:not(.active) .cursor::after {
visibility: hidden;
}
document.querySelectorAll(".typewriter-cycle").forEach((cycle) => {
const items = cycle.querySelectorAll("typewritten-text")
for (let i = 0; i < items.length; ++i) {
const cur = items[i]
const next = items[(i + 1) % items.length]
cur.addEventListener("typed", () => setTimeout(() => cur.erase(), cur.repeatInterval))
cur.addEventListener("erased", () => {
cur.classList.remove("active")
next.classList.add("active")
setTimeout(() => next.type(), next.repeatInterval)
})
}
})
Dialog
Dialog is often portrayed as typewritten text in games. Using the typed
event, you can chain several together.
View the Dialog demo on Codepen!
Hi! I'm Janet, the new gal on the team. What's your name?
I'm Dinesh, the UI designer. The first thing you should know is our team lead is always at least five minutes late.
const janet = document.querySelector("#janet typewritten-text")
const dinesh = document.querySelector("#dinesh typewritten-text")
janet?.addEventListener("typed", () => {
timeout = setTimeout(() => dinesh?.type(), dinesh?.repeatInterval)
})