Live Demo & Generator | Follow me on Twitter @branmcconnell
Typed.css types and deletes any number of strings, with a blinking caret, adjustable speeds, timed pauses, per-string styles, and accessible alt text. No JavaScript runs in the browser: the result is ordinary CSS animations, so the text works anywhere CSS does.
Every version below produces the same CSS, so pick whichever fits your stack. The live demo shows every example in each language.
- Getting started: Plain CSS, SCSS, Tailwind CSS, Less, Stylus, JavaScript
- Arguments: strings, speeds, options
- Syntax by language
- Pauses and line breaks
- Styling the text and caret
- Browser support
Use the generator to set up your typewriter, copy the raw CSS into your stylesheet, and add the class it gives you to an element:
<h1 class="typed-text"></h1>Download typed.scss into your project, import it, and include the mixin in any selector:
@import 'typed';
.headline {
@include typed("Hello", "World");
}Works with Dart Sass and LibSass.
npm install @typed-css/tailwindAdd your typewriters under theme.typed and register the plugin. Each entry becomes a typed-<name> class:
// tailwind.config.js
import typed from '@typed-css/tailwind';
export default {
theme: {
typed: {
headline: { strings: ['Hello', 'World'], speeds: 2 },
},
},
plugins: [typed],
};<h1 class="typed-headline text-purple-600"></h1>Works with Tailwind v3 and v4. In v4, load the config from your CSS with @config "./tailwind.config.js";.
npm install @typed-css/less@plugin "@typed-css/less";
.headline {
typed("Hello", "World");
}Requires Less 3.5 or newer. You can also pass the plugin through the Less API: less.render(source, { plugins: [typed] }).
npm install @typed-css/stylusRegister the plugin wherever you compile Stylus (the JavaScript API, or the use option of stylus-loader):
import stylus from 'stylus';
import typed from '@typed-css/stylus';
stylus(source).use(typed()).render(callback);.headline
typed("Hello", "World")For build scripts, CSS-in-JS, or anything else that can take a CSS string:
npm install @typed-css/coreimport { typed, toCSS } from '@typed-css/core';
const css = toCSS(typed({ strings: ['Hello', 'World'] }), '.headline');toCSSObject() returns the same styles as objects, for CSS-in-JS libraries.
Every version takes the same three things, in this order:
- strings (required): the text to type, one or more strings.
- speeds (optional): how fast to type and delete.
- options (optional): everything else.
In SCSS, Less, and Stylus, these are positional arguments: typed("One", "Two", speeds, options). To set options while keeping the default speeds, pass null for the speeds. In JavaScript and Tailwind, they're the strings, speeds, and options keys of one object.
Pass plain strings, or give a string its own styles. When some strings have styles, properties they don't set are reset for the others, so styles never leak from one string into the next.
All speeds are in seconds, written without units.
| Speed | JS / Tailwind | Default | What it controls |
|---|---|---|---|
type |
type |
.1 |
Time to type one character |
pause-typed |
pauseTyped |
2 |
Pause after a string is fully typed |
delete |
delete |
.08 |
Time to delete one character |
pause-deleted |
pauseDeleted |
1 |
Pause after a string is fully deleted |
You can give speeds three ways:
- A number multiplies the overall speed:
2is twice as fast,.5is half speed. - A list of up to four values, in the order above. Use
nullto keep a default. - A map that overrides only the speeds you name.
| Option | JS / Tailwind | Default | What it does |
|---|---|---|---|
iterations |
iterations |
infinite |
How many times to loop. A finite count ends on the end-on string. |
end-on |
endOn |
first string | The string left showing when a finite animation ends: a string, a 1-based index into your strings, or a string with its own styles. |
end-styles |
endStyles |
none | Styles applied once a finite animation finishes. |
styles |
styles |
none | Styles applied to every string. |
caret |
caret |
true |
Shows the blinking caret. |
caret-speed |
caretSpeed |
.75 |
Seconds per caret blink. |
caret-width |
caretWidth |
1ch |
Caret width (any length unit). |
caret-color |
caretColor |
currentColor |
Caret color. It follows the text color by default. |
caret-space |
caretSpace |
.1ch |
Gap between the text and the caret. |
delay |
delay |
1 |
Seconds before the animation starts. |
prefix |
prefix |
none | Static text shown before the typed text. |
alt-text |
altText |
the end-on string |
Text read by screen readers. |
name |
name |
generated | Custom animation name. In Tailwind it defaults to typed-<name>. |
type-pausing |
typePausing |
true |
Turns the <[N]> pause syntax on or off. |
type-pausing-default |
typePausingDefault |
"fwd" |
Direction for pauses written without an underscore: "fwd", "bwd", or "both". |
The arguments are the same everywhere; only the way you write lists, maps, and styled strings differs.
| SCSS | Less | Stylus | JavaScript / Tailwind | |
|---|---|---|---|---|
| Call | @include typed(…); |
typed(…); |
typed(…) |
typed({ strings, speeds, options }) |
| Speeds list | [.1, null, null, .5] |
.1 null null .5 |
.1 null null .5 |
[0.1, null, null, 0.5] |
| Speeds map | (type: .1, pause-deleted: .5) |
{ type: .1; pause-deleted: .5; } |
{ type: .1, pause-deleted: .5 } |
{ type: 0.1, pauseDeleted: 0.5 } |
| Options | (iterations: 2, caret: false) |
{ iterations: 2; caret: false; } |
{ iterations: 2, caret: false } |
{ iterations: 2, caret: false } |
| Styled strings | ("Red": (color: red), "Blue": (color: blue)) |
{ text: "Red"; color: red; }, … |
{ text: "Red", color: red }, … |
{ text: 'Red', styles: { color: 'red' } }, … |
Map options (styles, end-on) |
styles: (font-weight: 700) |
styles { font-weight: 700; } |
styles: { font-weight: 700 } |
styles: { 'font-weight': 700 } |
A few language-specific details:
- SCSS: styled strings use one map for all strings. Give strings with no styles of their own an empty map,
(). - Less: options that are maps are written as nested blocks without a colon, for example
end-on { text: "Done!"; color: green; }. - Stylus:
- Wrap values containing commas in
unquote(), for examplefont-family: unquote('"Times New Roman", serif'). - For strings containing quotes, use the other quote character rather than a backslash escape.
- Wrap values containing commas in
Pauses: write <[N]> inside a string to pause for as long as typing N characters would take. For example, "Roads?<[15]> Where we're going…". By default, pauses apply only while typing. To choose a direction, add an underscore:
| Syntax | Pauses while |
|---|---|
<[_3]> |
typing |
<[3_]> |
deleting |
<[_3_]> |
both |
Line breaks: use \A in SCSS, Less, and Stylus, or \n in JavaScript. For example, "Line one\ALine two".
The typed text is drawn in the element's ::before pseudo-element, and the caret in ::after. Style those directly:
.headline {
@include typed("Hello", "World");
&::before { font-weight: 700; } // the text
&::after { border-right-width: 2px; } // the caret
}In Tailwind, use the before: and after: variants, for example before:font-bold.
To type in the middle of a sentence, apply the typewriter to an inline element:
<p>Go ahead, make my <span class="typed-time-of-day"></span>!</p>All modern browsers: Chrome, Edge, Firefox, Safari, Opera, and their mobile versions.
This repository holds every version of Typed.css and the typedcss.com website. To work on it:
npm install
npm run dev # the website, at http://localhost:5173
npm testHi there, I'm Brandon! 👨🏻💻
It's nice to meet you. So far, Typed.css has been a one-dev show, but I'm always open to feedback or help from others to level up software. If you have ideas for improvements or want to join in growing this module, please email me directly at brandon[at]dreamthinkbuild.com or open an issue here on GitHub with your idea(s), and I'll get back to you as soon as I can. Cheers! 🍻