npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@andreasnicolaou/typewriter-ts

v1.0.0

Published

A tiny, dependency-free TypeScript typewriter for browsers and Node.

Downloads

73

Readme

TypeScript ESLint Prettier Vitest Maintenance GitHub Actions Workflow Status GitHub License


typewriter-ts animates strings one character at a time. It is dependency-free, Unicode-safe, framework-agnostic, typed, and ships ESM, CommonJS, UMD, minified UMD, and TypeScript declarations.

import { Typewriter } from '@andreasnicolaou/typewriter-ts';

new Typewriter(document.querySelector('.headline')!, {
  delay: 45,
  cursor: '▋',
})
  .type('Small package. Big personality.')
  .pause(900)
  .delete()
  .type('Works everywhere.')
  .start();
  • Dependency-free runtime — no framework or utility dependencies.
  • Compact bundle — Bundlewatch checks every JavaScript output.
  • Works everywhere — render into a DOM element or any (text) => void callback, including Node, terminals, canvas, and framework state.
  • Unicode-safe — text advances one grapheme cluster at a time, so emoji sequences such as 👨‍👩‍👧, flags, and combining accents are never split mid-character.
  • Typed API — TypeScript declarations are included for both ESM and CommonJS consumers.
  • Multiple builds — ESM, CommonJS, UMD, and minified UMD.

The default export is the same class, if you prefer it:

import Typewriter from '@andreasnicolaou/typewriter-ts';

Getting Started

Installation

npm install @andreasnicolaou/typewriter-ts

Live demo

The demo is hosted on GitHub Pages:

andreasnicolaou.github.io/typewriter-ts

You can also serve the project with any static file server. The page uses the local build first and falls back to unpkg, so the playground always matches the checked-out source.

Browser Usage

<p id="headline"></p>

<script src="https://unpkg.com/@andreasnicolaou/typewriter-ts@latest/dist/index.umd.min.js"></script>
<script>
  const { Typewriter: Writer } = window.typewriter;

  new Writer(document.querySelector('#headline'), { delay: 45, cursor: '▋' }).type('Hello from unpkg!').start();
</script>

API

const writer = new Typewriter(target, options);

target can be an HTMLElement, an HTMLInputElement, an HTMLTextAreaElement, another compatible text object, or a (text: string) => void callback.

new Typewriter(document.querySelector('.headline')!);
new Typewriter(document.querySelector('input')!);
new Typewriter((text) => process.stdout.write(`\r${text}`));

Typewriter is the library's API. Create an instance with new Typewriter(target, options).

Options

  • delay: milliseconds between typed characters. Defaults to 40.
  • deleteDelay: milliseconds between deleted characters. Defaults to half of delay.
  • initial: text the writer starts from, so content already in the target is kept instead of cleared. Defaults to ''.
  • jitter: randomises every character delay by up to this fraction of itself, for a human cadence. Defaults to 0; 0.3 spreads a 40 ms delay across 28–52 ms. Values above 1 are clamped.
  • cursor: a string displayed while writing. Defaults to '|'; set false to disable it.
  • cursorDelay: milliseconds between cursor blinks. Defaults to 500.
  • cursorBlink: whether the cursor blinks. Defaults to true; set false for a steady cursor.
  • loop: true to repeat indefinitely, or a number for total runs.
  • onLoop(writer, run): called after every completed cycle, including the final one.
  • onComplete(writer, runs): called once when the final cycle completes. It does not run for an infinite loop.

Instance Methods

  • type(text, options?): queues Unicode-safe text typing. Use delay to override the character delay.
  • type(strings, options?): queues a string array, deleting between the strings. The last string stays on screen unless the writer loops, so a queue that stops ends on its final phrase. Array options are delay, pause (milliseconds), and delete (defaults to true; set false to concatenate the strings instead).
  • delete(count?, delay?): queues deletion of count characters, never more than the text currently holds. Omit the count to remove all of it.
  • pause(ms): queues an idle period.
  • start(): starts or resumes the queue. Once the queue has run to completion it stays idle until you queue more work or call reset().
  • stop(): pauses after the current character.
  • reset(): clears text and restarts from the first queued action.
  • destroy(): stops permanently and releases the pending timer. Later calls no longer render.

The text getter returns the current value without the cursor. running reports whether a queued run is active.

Characters are counted as grapheme clusters, so delete(1) removes a whole 👨‍👩‍👧 rather than one of its code points. Where Intl.Segmenter is unavailable, the writer falls back to code points.

Awaiting a run

finished sits at the end of a chain and resolves with the writer, so awaiting a run does not break the chain:

await new Typewriter(target, { delay: 30 }).type('Fetching results…').start().finished;

const writer = await new Typewriter(target).type('One moment…').start().finished;
writer.delete().type('Ready.').start();

Queueing more work hands out a fresh promise for that next run, destroy() settles a pending one, and an infinite loop never settles. The writer itself is deliberately not await-able: a thenable instance would be assimilated by Promise.resolve, Promise.all, and any async function that merely returns it, silently running the animation and handing back something that is no longer a Typewriter.

Rich text

The core deliberately writes plain text; it does not accept HTML or use innerHTML. Typing partial markup would produce broken, unsafe DOM states. For styled words, render the writer's text through a callback in your framework or use separate DOM elements for the styled content.

Accessibility

Animated text is motion, and some readers ask their system not to show it. The library never reads document, so the check belongs in your code, next to the rest of your preference handling:

const target = document.querySelector('.headline')!;
const message = 'Small package. Big personality.';

if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  target.textContent = message;
} else {
  new Typewriter(target).type(message).start();
}

When a live region announces the target, prefer a steady cursor (cursorBlink: false) so assistive technology is not re-notified on every blink.

Framework integration

typewriter-ts works with React, Vue, Angular, Svelte, and other frameworks because its target is just a text output. Create an instance after the target element has mounted, and call destroy() when its owner unmounts.

For server rendering or non-DOM environments, use a callback target. The library never reads document, so it is safe to import in Node and on the server.

The writer owns the target's textContent while active. Avoid rendering competing text into the same element until the animation is stopped or destroyed. When the element is already rendered with text — from the server, or as a placeholder — pass that text as initial so the first frame continues from it instead of clearing it.

Build formats

| Format | File | Best for | | ------------ | ----------------------- | ---------------------------- | | ESM | dist/index.js | Modern bundlers and Node ESM | | CommonJS | dist/index.cjs | require() compatibility | | UMD | dist/index.umd.js | Plain browser script tags | | UMD minified | dist/index.umd.min.js | unpkg and other CDNs |

Development

npm install
npm run check

check runs ESLint, Prettier validation, tsc --noEmit, Vitest with its coverage thresholds, the two-stage TypeScript/Rollup build, and Bundlewatch.

License

typewriter-ts is licensed under the MIT License © Andreas Nicolaou.