Components / Motion / Typewriter

Typewriter beta

A sentence that types itself, deletes itself, and types the next one — with a patient caret.

Preview

Blocks compose

<p class="k-type" data-type data-phrases='["a dashboard", "a settings page", "a pricing table", "an app shell"]' aria-label="Blocks compose a dashboard, a settings page, a pricing table, an app shell." style="font-size:21px;font-weight:600;letter-spacing:-.02em">
  Blocks compose <span aria-hidden="true"><span data-type-out></span><span class="k-type-caret"></span></span>
</p>
/* KATA Typewriter — types, rests, deletes, repeats. */
.k-type{font-family:var(--k-font)}
.k-type-caret{display:inline-block;width:2.5px;height:1.02em;margin-inline-start:2px;background:var(--k-ink);vertical-align:-0.14em;animation:k-type-blink 1.05s steps(1) infinite}
@keyframes k-type-blink{50%{opacity:0}}
@media (prefers-reduced-motion:reduce){.k-type-caret{animation:none}}
// KATA Typewriter — types, rests, deletes, moves on.
document.querySelectorAll('[data-type]').forEach(function (el) {
  var out = el.querySelector('[data-type-out]');
  var phrases = [];
  try { phrases = JSON.parse(el.getAttribute('data-phrases') || '[]'); } catch (e) { phrases = []; }
  if (!phrases.length) return;
  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { out.textContent = phrases[0]; return; }
  var pi = 0, ci = 0, deleting = false;
  function tick() {
    var full = phrases[pi];
    if (deleting) {
      ci--;
      out.textContent = full.slice(0, ci);
      if (ci === 0) { deleting = false; pi = (pi + 1) % phrases.length; setTimeout(tick, 420); return; }
      setTimeout(tick, 34);
    } else {
      ci++;
      out.textContent = full.slice(0, ci);
      if (ci === full.length) { deleting = true; setTimeout(tick, 1850); return; }
      setTimeout(tick, 68 + Math.random() * 60);
    }
  }
  tick();
});

Installation

CLI

node kata-ui.mjs add typewriter

Writes the part’s CSS and JS into your project. New to the CLI? See the CLI doc.

Manual

  1. Load tokens.css once, before any part.
  2. Copy the CSS tab into your stylesheet.
  3. Copy the HTML where it belongs. The part has a script — include it after the markup; it initializes itself.

Usage

Phrases live in the data-phrases attribute as a JSON list. The rhythm is the design: type at reading-aloud speed, rest on the finished line, delete faster than you typed. One typewriter per page — two is a typing pool.

Accessibility

The animation is a performance of text that must also exist statically: the element carries an aria-label with the full sentence, the animated text is aria-hidden, and under prefers-reduced-motion the first phrase simply stands typed. Never put information only in a later phrase.

Anatomy

The public surface of this part, read from its own source. Class names are the API — the CSS tab above is the full contract.

.k-typeclass
.k-type-caretclass
data-typedata hook
data-phrasesdata hook
data-type-outdata hook
aria-labelARIA
aria-hiddenARIA

Related parts